Skip to content

Commit 7593e31

Browse files
committed
Mitigate possible disk image misuse
If the user specifies a Raw disk image, there are possible scenarios where the guest could misuse the disk image. In one such scenario, the guest could write a different image header into the first sector of the file. Mitigate this by forcing the guest to write to the first four bytes as a whole, and not byte-by-byte in any order. Additionally, mitigate this by verifying that if the offset into the disk image is zero and the length of the buffer to be written is greater than or equal to four, probe the buffer's first four bytes to make sure it's not a QCOW magic string ("QFI\xfb"). If any of these conditions are met, then reject the write. Signed-off-by: Jake Correnti <[email protected]>
1 parent 71d75e7 commit 7593e31

File tree

1 file changed

+62
-6
lines changed

1 file changed

+62
-6
lines changed

src/devices/src/virtio/file_traits.rs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ use std::io::{Error, ErrorKind, Result};
77
use std::os::unix::io::AsRawFd;
88

99
#[cfg(feature = "blk")]
10-
use imago::io_buffers::{IoVector, IoVectorMut};
10+
use imago::io_buffers::{IoBuffer, IoVector, IoVectorMut};
1111
use vm_memory::VolatileSlice;
1212

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

1515
use super::bindings::{off64_t, pread64, preadv64, pwrite64, pwritev64};
1616
#[cfg(feature = "blk")]
17-
use super::block::device::DiskProperties;
17+
use super::block::{device::DiskProperties, disk};
1818

1919
/// A trait for setting the size of a file.
2020
/// This is equivalent to File's `set_len` method, but
@@ -440,9 +440,12 @@ impl FileReadWriteAtVolatile for DiskProperties {
440440
}
441441

442442
fn write_at_volatile(&self, slice: VolatileSlice, offset: u64) -> Result<usize> {
443-
let slices = &[&slice];
444-
let iovec = IoVector::from_volatile_slice(slices);
445-
self.file().writev(iovec.0, offset)?;
443+
let slices = &[slice];
444+
445+
let mut _copied_head: Option<IoBuffer> = None;
446+
let iovec = create_iovec(self, slices, offset, &mut _copied_head)?;
447+
448+
self.file().writev(iovec, offset)?;
446449
Ok(slice.len())
447450
}
448451

@@ -451,7 +454,9 @@ impl FileReadWriteAtVolatile for DiskProperties {
451454
return Ok(0);
452455
}
453456

454-
let (iovec, _guard) = IoVector::from_volatile_slice(bufs);
457+
let mut _copied_head: Option<IoBuffer> = None;
458+
let iovec = create_iovec(self, bufs, offset, &mut _copied_head)?;
459+
455460
let full_length = iovec
456461
.len()
457462
.try_into()
@@ -460,3 +465,54 @@ impl FileReadWriteAtVolatile for DiskProperties {
460465
Ok(full_length)
461466
}
462467
}
468+
469+
#[cfg(feature = "blk")]
470+
fn create_iovec<'a>(
471+
disk: &DiskProperties,
472+
bufs: &'a [VolatileSlice],
473+
offset: u64,
474+
head_buffer: &'a mut Option<IoBuffer>,
475+
) -> Result<IoVector<'a>> {
476+
match disk.image_format() {
477+
disk::ImageType::Raw => {
478+
let (iovec, _guard) = IoVector::from_volatile_slice(bufs);
479+
guard_header_bytes(disk.file(), iovec, offset, head_buffer)
480+
}
481+
_ => Ok(IoVector::from_volatile_slice(bufs).0),
482+
}
483+
}
484+
485+
#[cfg(feature = "blk")]
486+
fn guard_header_bytes<'a>(
487+
image: &imago::SyncFormatAccess<imago::file::File>,
488+
iovec: IoVector<'a>,
489+
offset: u64,
490+
head_buffer: &'a mut Option<IoBuffer>,
491+
) -> Result<IoVector<'a>> {
492+
if (1..4).contains(&offset) || (offset + iovec.len() < 4) {
493+
// Make sure the guest writes to the first four bytes as a whole. This is done to prevent a
494+
// Raw disk image byte-by-byte, and in any order, writing the header of a formatted disk
495+
// image into the first sector of the disk image.
496+
return Err(Error::new(
497+
ErrorKind::InvalidInput,
498+
"partial write to first four bytes of disk image",
499+
));
500+
}
501+
502+
if offset > 0 {
503+
return Ok(iovec);
504+
}
505+
506+
let (head, tail) = iovec.split_at(std::cmp::max(image.mem_align(), 4) as u64);
507+
*head_buffer = Some(head.try_into_owned(image.mem_align())?);
508+
let head_slice = head_buffer.as_ref().unwrap().as_ref().into_slice();
509+
510+
if head_slice.get(0..4) == Some(&disk::QCOW_MAGIC.to_be_bytes()) {
511+
return Err(Error::new(
512+
ErrorKind::InvalidData,
513+
"writing QCOW2 header to disk image",
514+
));
515+
}
516+
517+
Ok(tail.with_inserted(0, head_slice))
518+
}

0 commit comments

Comments
 (0)