From c569a4cbd4f0ad00a48e0e8a2801abd9333d4e50 Mon Sep 17 00:00:00 2001 From: Anatol Belski Date: Sat, 7 Feb 2026 00:06:39 +0100 Subject: [PATCH] block: qcow: Return zeros for reads beyond backing file size When an overlay QCOW2 image is larger than its backing file, reads from offsets beyond the backing file virtual size would previously fail with an I/O error. The backing file virtual size is determined at open time and stored for bounds checking during read operations: - If the entire read is beyond the backing size, return all zeros - If the read spans the boundary, read available data from backing and fill the remainder with zeros Signed-off-by: Anatol Belski --- block/src/qcow/mod.rs | 46 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/block/src/qcow/mod.rs b/block/src/qcow/mod.rs index 300cd8bc3..e9f45cbf1 100644 --- a/block/src/qcow/mod.rs +++ b/block/src/qcow/mod.rs @@ -774,6 +774,7 @@ impl BackingFileOps for RawFile { /// Backing file wrapper struct BackingFile { inner: Box, + virtual_size: u64, } impl BackingFile { @@ -804,22 +805,48 @@ impl BackingFile { None => detect_image_type(&mut raw_file)?, }; - let inner: Box = match backing_format { - ImageType::Raw => Box::new(raw_file), + let (inner, virtual_size): (Box, u64) = match backing_format { + ImageType::Raw => { + let size = raw_file + .seek(SeekFrom::End(0)) + .map_err(Error::BackingFileIo)?; + raw_file.rewind().map_err(Error::BackingFileIo)?; + (Box::new(raw_file), size) + } ImageType::Qcow2 => { let backing_qcow = QcowFile::from_with_nesting_depth(raw_file, max_nesting_depth - 1) .map_err(|e| Error::BackingFileOpen(Box::new(e)))?; - Box::new(backing_qcow) + let size = backing_qcow.virtual_size(); + (Box::new(backing_qcow), size) } }; - Ok(Some(Self { inner })) + Ok(Some(Self { + inner, + virtual_size, + })) } + /// Read from backing file, returning zeros for any portion beyond backing file size. #[inline] fn read_at(&mut self, address: u64, buf: &mut [u8]) -> std::io::Result<()> { - self.inner.read_at(address, buf) + if address >= self.virtual_size { + // Entire read is beyond backing file + buf.fill(0); + return Ok(()); + } + + let available = (self.virtual_size - address) as usize; + if available >= buf.len() { + // Entire read is within backing file + self.inner.read_at(address, buf) + } else { + // Partial read, fill the rest with zeroes + self.inner.read_at(address, &mut buf[..available])?; + buf[available..].fill(0); + Ok(()) + } } } @@ -827,6 +854,7 @@ impl Clone for BackingFile { fn clone(&self) -> Self { Self { inner: self.inner.clone_box(), + virtual_size: self.virtual_size, } } } @@ -1108,8 +1136,12 @@ impl QcowFile { } pub fn set_backing_file(&mut self, backing: Option>) { - self.backing_file = backing.map(|b| BackingFile { - inner: Box::new(*b), + self.backing_file = backing.map(|b| { + let virtual_size = b.virtual_size(); + BackingFile { + inner: Box::new(*b), + virtual_size, + } }); }