Skip to content

Commit 0b8eeef

Browse files
committed
Mitigate disk image misuse
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]>
1 parent 5571dd1 commit 0b8eeef

File tree

1 file changed

+57
-5
lines changed

1 file changed

+57
-5
lines changed

src/devices/src/virtio/file_traits.rs

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ use std::os::unix::io::AsRawFd;
88

99
use vm_memory::VolatileSlice;
1010

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

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

437438
fn write_at_volatile(&self, slice: VolatileSlice, offset: u64) -> Result<usize> {
438439
let slices = &[&slice];
439-
let iovec = imago::io_buffers::IoVector::from_volatile_slice(slices);
440-
self.writev(iovec.0, offset)?;
440+
441+
let (mut iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(slices);
442+
guard_header_bytes(&iovec, offset)?;
443+
444+
let mut _copied_head = None;
445+
if offset == 0 {
446+
let (head, tail) = iovec.split_at(std::cmp::max(self.mem_align(), 4) as u64);
447+
_copied_head = Some(head.try_into_owned(self.mem_align())?);
448+
let head_slice = _copied_head.as_ref().unwrap().as_ref().into_slice();
449+
#[cfg(feature = "blk")]
450+
if head_slice.get(0..4) == Some(&disk::QCOW_MAGIC.to_be_bytes()) {
451+
return Err(Error::new(
452+
ErrorKind::InvalidData,
453+
"writing QCOW2 header to file",
454+
));
455+
}
456+
457+
iovec = tail.with_inserted(0, head_slice);
458+
}
459+
460+
self.writev(iovec, offset)?;
441461
Ok(slice.len())
442462
}
443463

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

449-
let (iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(bufs);
469+
let (mut iovec, _guard) = imago::io_buffers::IoVector::from_volatile_slice(bufs);
470+
guard_header_bytes(&iovec, offset)?;
471+
472+
let mut _copied_head = None;
473+
if offset == 0 {
474+
let (head, tail) = iovec.split_at(std::cmp::max(self.mem_align(), 4) as u64);
475+
_copied_head = Some(head.try_into_owned(self.mem_align())?);
476+
let head_slice = _copied_head.as_ref().unwrap().as_ref().into_slice();
477+
#[cfg(feature = "blk")]
478+
if head_slice.get(0..4) == Some(&disk::QCOW_MAGIC.to_be_bytes()) {
479+
return Err(Error::new(
480+
ErrorKind::InvalidData,
481+
"writing QCOW2 header to disk image",
482+
));
483+
}
484+
485+
iovec = tail.with_inserted(0, head_slice);
486+
}
487+
450488
let full_length = iovec
451489
.len()
452490
.try_into()
@@ -455,3 +493,17 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess<imago::file::File> {
455493
Ok(full_length)
456494
}
457495
}
496+
497+
fn guard_header_bytes(iovec: &imago::io_buffers::IoVector, offset: u64) -> Result<()> {
498+
if (1..4).contains(&offset) || (offset + iovec.len() < 4) {
499+
// Make sure the guest writes to the first four bytes as a whole. This is done to prevent a
500+
// Raw disk image byte-by-byte, and in any order, writing the header of a formatted disk
501+
// image into the first sector of the disk image.
502+
return Err(Error::new(
503+
ErrorKind::InvalidInput,
504+
"partial write to first four bytes of disk image",
505+
));
506+
}
507+
508+
Ok(())
509+
}

0 commit comments

Comments
 (0)