diff --git a/src/devices/src/virtio/file_traits.rs b/src/devices/src/virtio/file_traits.rs index cad0ff93..17246985 100644 --- a/src/devices/src/virtio/file_traits.rs +++ b/src/devices/src/virtio/file_traits.rs @@ -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 @@ -436,8 +437,27 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess { fn write_at_volatile(&self, slice: VolatileSlice, offset: u64) -> Result { 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()) } @@ -446,7 +466,25 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess { 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() @@ -455,3 +493,17 @@ impl FileReadWriteAtVolatile for imago::SyncFormatAccess { 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(()) +}