Skip to content

Commit

Permalink
block: qcow: limit max nesting depth for backing file
Browse files Browse the repository at this point in the history
Impose a limit on the maximum nesting of file formats that can open more
files. For example, a qcow2 file can have a backing file, which could be
another qcow2 file with a backing file (or even the same file as the
original), potentially causing unbounded recursion.

This commit is based on crosvm implementation:
https://chromium.googlesource.com/crosvm/crosvm/+/eb1640e301d66c06e0e0a07886946830f3f2f4fe

Fixes: cloud-hypervisor#6472

Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
  • Loading branch information
yukiiiteru committed Jun 11, 2024
1 parent 6a08133 commit 04ab36e
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 12 deletions.
17 changes: 12 additions & 5 deletions block/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ use vmm_sys_util::{ioctl_io_nr, ioctl_ioc_nr};
const SECTOR_SHIFT: u8 = 9;
pub const SECTOR_SIZE: u64 = 0x01 << SECTOR_SHIFT;

/// Nesting depth limit for disk formats that can open other disk files.
pub const MAX_NESTING_DEPTH: u32 = 10;

#[derive(Error, Debug)]
pub enum Error {
#[error("Guest gave us bad memory addresses")]
Expand Down Expand Up @@ -793,14 +796,18 @@ pub trait BlockBackend: Read + Write + Seek + Send + Debug {
}

/// Inspect the image file type and create an appropriate disk file to match it.
pub fn create_disk_file(mut file: File, direct_io: bool) -> Result<Box<dyn BlockBackend>, Error> {
pub fn create_disk_file(
mut file: File,
direct_io: bool,
max_nesting_depth: u32,
) -> Result<Box<dyn BlockBackend>, Error> {
let image_type = detect_image_type(&mut file).map_err(Error::DetectImageType)?;

Ok(match image_type {
ImageType::Qcow2 => {
Box::new(QcowFile::from(RawFile::new(file, direct_io)).map_err(Error::QcowError)?)
as Box<dyn BlockBackend>
}
ImageType::Qcow2 => Box::new(
QcowFile::from_with_nesting_depth(RawFile::new(file, direct_io), max_nesting_depth)
.map_err(Error::QcowError)?,
) as Box<dyn BlockBackend>,
ImageType::FixedVhd => {
Box::new(FixedVhd::new(file).map_err(Error::FixedVhdError)?) as Box<dyn BlockBackend>
}
Expand Down
36 changes: 29 additions & 7 deletions block/src/qcow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ pub enum Error {
InvalidRefcountTableOffset,
#[error("Invalid refcount table size: {0}")]
InvalidRefcountTableSize(u64),
#[error("maximum disk nesting depth exceeded")]
MaxNestingDepthExceeded,
#[error("No free clusters")]
NoFreeClusters,
#[error("No refcount clusters")]
Expand Down Expand Up @@ -439,7 +441,15 @@ pub struct QcowFile {

impl QcowFile {
/// Creates a QcowFile from `file`. File must be a valid qcow2 image.
pub fn from(mut file: RawFile) -> Result<QcowFile> {
///
/// Additionally, max nesting depth of this qcow2 image will be set to default value 10.
pub fn from(file: RawFile) -> Result<QcowFile> {
Self::from_with_nesting_depth(file, super::MAX_NESTING_DEPTH)
}

/// Creates a QcowFile from `file` and with a max nesting depth. File must be a valid qcow2
/// image.
pub fn from_with_nesting_depth(mut file: RawFile, max_nesting_depth: u32) -> Result<QcowFile> {
let header = QcowHeader::new(&mut file)?;

// Only v2 and v3 files are supported.
Expand All @@ -466,13 +476,17 @@ impl QcowFile {
let direct_io = file.is_direct();

let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() {
if max_nesting_depth == 0 {
return Err(Error::MaxNestingDepthExceeded);
}
let path = backing_file_path.clone();
let backing_raw_file = OpenOptions::new()
.read(true)
.open(path)
.map_err(Error::BackingFileIo)?;
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let backing_file =
crate::create_disk_file(backing_raw_file, direct_io, max_nesting_depth - 1)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(backing_file)
} else {
None
Expand Down Expand Up @@ -621,14 +635,16 @@ impl QcowFile {
file: RawFile,
version: u32,
backing_file_name: &str,
backing_file_max_nesting_depth: u32,
) -> Result<QcowFile> {
let direct_io = file.is_direct();
let backing_raw_file = OpenOptions::new()
.read(true)
.open(backing_file_name)
.map_err(Error::BackingFileIo)?;
let backing_file = crate::create_disk_file(backing_raw_file, direct_io)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let backing_file =
crate::create_disk_file(backing_raw_file, direct_io, backing_file_max_nesting_depth)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file
.size()
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Expand Down Expand Up @@ -1778,11 +1794,17 @@ where
/// Copy the contents of a disk image in `src_file` into `dst_file`.
/// The type of `src_file` is automatically detected, and the output file type is
/// determined by `dst_type`.
pub fn convert(mut src_file: RawFile, dst_file: RawFile, dst_type: ImageType) -> Result<()> {
pub fn convert(
mut src_file: RawFile,
dst_file: RawFile,
dst_type: ImageType,
src_max_nesting_depth: u32,
) -> Result<()> {
let src_type = detect_image_type(&mut src_file)?;
match src_type {
ImageType::Qcow2 => {
let mut src_reader = QcowFile::from(src_file)?;
let mut src_reader =
QcowFile::from_with_nesting_depth(src_file, src_max_nesting_depth)?;
convert_reader(&mut src_reader, dst_file, dst_type)
}
ImageType::Raw => {
Expand Down

0 comments on commit 04ab36e

Please sign in to comment.