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 <anbelski@linux.microsoft.com>
This commit is contained in:
parent
2c49f4f4f3
commit
c569a4cbd4
1 changed files with 39 additions and 7 deletions
|
|
@ -774,6 +774,7 @@ impl BackingFileOps for RawFile {
|
|||
/// Backing file wrapper
|
||||
struct BackingFile {
|
||||
inner: Box<dyn BackingFileOps>,
|
||||
virtual_size: u64,
|
||||
}
|
||||
|
||||
impl BackingFile {
|
||||
|
|
@ -804,22 +805,48 @@ impl BackingFile {
|
|||
None => detect_image_type(&mut raw_file)?,
|
||||
};
|
||||
|
||||
let inner: Box<dyn BackingFileOps> = match backing_format {
|
||||
ImageType::Raw => Box::new(raw_file),
|
||||
let (inner, virtual_size): (Box<dyn BackingFileOps>, 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<Box<Self>>) {
|
||||
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,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue