block: qcow: limit max nesting depth for backing file

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:
eb1640e301

Fixes: #6472

Signed-off-by: Yu Li <liyu.yukiteru@bytedance.com>
This commit is contained in:
Yu Li 2024-05-24 19:21:13 +08:00 committed by Liu Wei
parent c206c14318
commit 220455caaf

View File

@ -31,13 +31,16 @@ use vmm_sys_util::{
pub use crate::qcow::raw_file::RawFile; pub use crate::qcow::raw_file::RawFile;
/// Nesting depth limit for disk formats that can open other disk files.
const MAX_NESTING_DEPTH: u32 = 10;
#[sorted] #[sorted]
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("Backing file io error: {0}")] #[error("Backing file io error: {0}")]
BackingFileIo(io::Error), BackingFileIo(io::Error),
#[error("Backing file open error: {0}")] #[error("Backing file open error: {0}")]
BackingFileOpen(Box<crate::Error>), BackingFileOpen(Box<Error>),
#[error("Backing file name is too long: {0} bytes over")] #[error("Backing file name is too long: {0} bytes over")]
BackingFileTooLong(usize), BackingFileTooLong(usize),
#[error("Compressed blocks not supported")] #[error("Compressed blocks not supported")]
@ -70,6 +73,8 @@ pub enum Error {
InvalidRefcountTableOffset, InvalidRefcountTableOffset,
#[error("Invalid refcount table size: {0}")] #[error("Invalid refcount table size: {0}")]
InvalidRefcountTableSize(u64), InvalidRefcountTableSize(u64),
#[error("Maximum disk nesting depth exceeded")]
MaxNestingDepthExceeded,
#[error("No free clusters")] #[error("No free clusters")]
NoFreeClusters, NoFreeClusters,
#[error("No refcount clusters")] #[error("No refcount clusters")]
@ -434,12 +439,20 @@ pub struct QcowFile {
// List of unreferenced clusters available to be used. unref clusters become available once the // List of unreferenced clusters available to be used. unref clusters become available once the
// removal of references to them have been synced to disk. // removal of references to them have been synced to disk.
avail_clusters: Vec<u64>, avail_clusters: Vec<u64>,
backing_file: Option<Box<dyn BlockBackend>>, backing_file: Option<Box<Self>>,
} }
impl QcowFile { impl QcowFile {
/// Creates a QcowFile from `file`. File must be a valid qcow2 image. /// 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, 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)?; let header = QcowHeader::new(&mut file)?;
// Only v2 and v3 files are supported. // Only v2 and v3 files are supported.
@ -466,14 +479,20 @@ impl QcowFile {
let direct_io = file.is_direct(); let direct_io = file.is_direct();
let backing_file = if let Some(backing_file_path) = header.backing_file_path.as_ref() { 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 path = backing_file_path.clone();
let backing_raw_file = OpenOptions::new() let backing_raw_file = OpenOptions::new()
.read(true) .read(true)
.open(path) .open(path)
.map_err(Error::BackingFileIo)?; .map_err(Error::BackingFileIo)?;
let backing_file = crate::create_disk_file(backing_raw_file, direct_io) let backing_file = Self::from_with_nesting_depth(
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?; RawFile::new(backing_raw_file, direct_io),
Some(backing_file) max_nesting_depth - 1,
)
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
Some(Box::new(backing_file))
} else { } else {
None None
}; };
@ -621,20 +640,22 @@ impl QcowFile {
file: RawFile, file: RawFile,
version: u32, version: u32,
backing_file_name: &str, backing_file_name: &str,
backing_file_max_nesting_depth: u32,
) -> Result<QcowFile> { ) -> Result<QcowFile> {
let direct_io = file.is_direct(); let direct_io = file.is_direct();
let backing_raw_file = OpenOptions::new() let backing_raw_file = OpenOptions::new()
.read(true) .read(true)
.open(backing_file_name) .open(backing_file_name)
.map_err(Error::BackingFileIo)?; .map_err(Error::BackingFileIo)?;
let backing_file = crate::create_disk_file(backing_raw_file, direct_io) let backing_file = Self::from_with_nesting_depth(
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?; RawFile::new(backing_raw_file, direct_io),
let size = backing_file backing_file_max_nesting_depth,
.size() )
.map_err(|e| Error::BackingFileOpen(Box::new(e)))?; .map_err(|e| Error::BackingFileOpen(Box::new(e)))?;
let size = backing_file.virtual_size();
let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?; let header = QcowHeader::create_for_size_and_path(version, size, Some(backing_file_name))?;
let mut result = QcowFile::new_from_header(file, header)?; let mut result = QcowFile::new_from_header(file, header)?;
result.backing_file = Some(backing_file); result.backing_file = Some(Box::new(backing_file));
Ok(result) Ok(result)
} }
@ -662,7 +683,7 @@ impl QcowFile {
Ok(qcow) Ok(qcow)
} }
pub fn set_backing_file(&mut self, backing: Option<Box<dyn BlockBackend>>) { pub fn set_backing_file(&mut self, backing: Option<Box<Self>>) {
self.backing_file = backing; self.backing_file = backing;
} }
@ -1778,11 +1799,17 @@ where
/// Copy the contents of a disk image in `src_file` into `dst_file`. /// 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 /// The type of `src_file` is automatically detected, and the output file type is
/// determined by `dst_type`. /// 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)?; let src_type = detect_image_type(&mut src_file)?;
match src_type { match src_type {
ImageType::Qcow2 => { 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) convert_reader(&mut src_reader, dst_file, dst_type)
} }
ImageType::Raw => { ImageType::Raw => {