Skip to content

Commit

Permalink
Mitigate disk image misuse
Browse files Browse the repository at this point in the history
Adds mitigations to prevent the guest from writing a formatted disk
image header into the first four bytes of the disk image.

Signed-off-by: Jake Correnti <[email protected]>
  • Loading branch information
jakecorrenti committed Nov 18, 2024
1 parent 5571dd1 commit 0b8eeef
Showing 1 changed file with 57 additions and 5 deletions.
62 changes: 57 additions & 5 deletions src/devices/src/virtio/file_traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ use std::os::unix::io::AsRawFd;

use vm_memory::VolatileSlice;

use libc::{c_int, c_void, read, readv, size_t, write, writev};

use super::bindings::{off64_t, pread64, preadv64, pwrite64, pwritev64};
#[cfg(feature = "blk")]
use crate::virtio::block::disk;
use libc::{c_int, c_void, read, readv, size_t, write, writev};

/// A trait for setting the size of a file.
/// This is equivalent to File's `set_len` method, but
Expand Down Expand Up @@ -436,8 +437,27 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess<imago::file::File> {

fn write_at_volatile(&self, slice: VolatileSlice, offset: u64) -> Result<usize> {
let slices = &[&slice];
let iovec = imago::io_buffers::IoVector::from_volatile_slice(slices);
self.writev(iovec.0, offset)?;

let (mut iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(slices);
guard_header_bytes(&iovec, offset)?;

let mut _copied_head = None;
if offset == 0 {
let (head, tail) = iovec.split_at(std::cmp::max(self.mem_align(), 4) as u64);
_copied_head = Some(head.try_into_owned(self.mem_align())?);
let head_slice = _copied_head.as_ref().unwrap().as_ref().into_slice();
#[cfg(feature = "blk")]
if head_slice.get(0..4) == Some(&disk::QCOW_MAGIC.to_be_bytes()) {
return Err(Error::new(
ErrorKind::InvalidData,
"writing QCOW2 header to file",
));
}

iovec = tail.with_inserted(0, head_slice);
}

self.writev(iovec, offset)?;
Ok(slice.len())
}

Expand All @@ -446,7 +466,25 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess<imago::file::File> {
return Ok(0);
}

let (iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(bufs);
let (mut iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(bufs);
guard_header_bytes(&iovec, offset)?;

let mut _copied_head = None;
if offset == 0 {
let (head, tail) = iovec.split_at(std::cmp::max(self.mem_align(), 4) as u64);
_copied_head = Some(head.try_into_owned(self.mem_align())?);
let head_slice = _copied_head.as_ref().unwrap().as_ref().into_slice();
#[cfg(feature = "blk")]
if head_slice.get(0..4) == Some(&disk::QCOW_MAGIC.to_be_bytes()) {
return Err(Error::new(
ErrorKind::InvalidData,
"writing QCOW2 header to disk image",
));
}

iovec = tail.with_inserted(0, head_slice);
}

let full_length = iovec
.len()
.try_into()
Expand All @@ -455,3 +493,17 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess<imago::file::File> {
Ok(full_length)
}
}

fn guard_header_bytes(iovec: &imago::io_buffers::IoVector, offset: u64) -> Result<()> {
if (1..4).contains(&offset) || (offset + iovec.len() < 4) {
// Make sure the guest writes to the first four bytes as a whole. This is done to prevent a
// Raw disk image byte-by-byte, and in any order, writing the header of a formatted disk
// image into the first sector of the disk image.
return Err(Error::new(
ErrorKind::InvalidInput,
"partial write to first four bytes of disk image",
));
}

Ok(())
}

0 comments on commit 0b8eeef

Please sign in to comment.