diff --git a/block/src/formats/vhdx/io.rs b/block/src/formats/vhdx/io.rs index 100a2316f..423215604 100644 --- a/block/src/formats/vhdx/io.rs +++ b/block/src/formats/vhdx/io.rs @@ -19,6 +19,8 @@ pub enum VhdxIoError { InvalidBatEntryState, #[error("Invalid BAT entry count")] InvalidBatIndex, + #[error("Buffer length does not match the requested sector count")] + InvalidBufferLength, #[error("Invalid disk size")] InvalidDiskSize, #[error("Failed reading sector blocks from file {0}")] @@ -95,6 +97,12 @@ pub(super) fn read( if disk_spec.has_parent { return Err(VhdxIoError::UnsupportedMode); } + let expected_len = sector_count + .checked_mul(disk_spec.logical_sector_size as u64) + .ok_or(VhdxIoError::InvalidBufferLength)?; + if buf.len() as u64 != expected_len { + return Err(VhdxIoError::InvalidBufferLength); + } let mut read_count: usize = 0; while sector_count > 0 { @@ -147,6 +155,12 @@ pub(super) fn write( if disk_spec.has_parent { return Err(VhdxIoError::UnsupportedMode); } + let expected_len = sector_count + .checked_mul(disk_spec.logical_sector_size as u64) + .ok_or(VhdxIoError::InvalidBufferLength)?; + if buf.len() as u64 != expected_len { + return Err(VhdxIoError::InvalidBufferLength); + } let mut write_count: usize = 0; while sector_count > 0 { @@ -274,4 +288,24 @@ mod tests { f.file().read_exact_at(&mut readback, DATA_OFFSET).unwrap(); assert_eq!(readback, data); } + + #[test] + fn read_short_buffer_is_rejected() { + let (f, disk_spec, bat) = fixture(); + + let mut buf = vec![0u8; SECTOR_SIZE as usize - 1]; + let err = read(&f, &mut buf, &disk_spec, &bat, 0, 1).unwrap_err(); + + assert!(matches!(err, VhdxIoError::InvalidBufferLength)); + } + + #[test] + fn write_short_buffer_is_rejected() { + let (f, mut disk_spec, mut bat) = fixture(); + + let data = vec![0xCDu8; SECTOR_SIZE as usize - 1]; + let err = write(&f, &data, &mut disk_spec, 0, &mut bat, 0, 1).unwrap_err(); + + assert!(matches!(err, VhdxIoError::InvalidBufferLength)); + } } diff --git a/block/src/formats/vhdx/parser.rs b/block/src/formats/vhdx/parser.rs index a739b1a6a..2b42be4fb 100644 --- a/block/src/formats/vhdx/parser.rs +++ b/block/src/formats/vhdx/parser.rs @@ -98,8 +98,18 @@ impl Read for Vhdx { /// Wrapper function to satisfy Read trait implementation for VHDx disk. /// Convert the offset to sector index and buffer length to sector count. fn read(&mut self, buf: &mut [u8]) -> IoResult { - let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64); - let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64; + let sector_size = self.disk_spec.logical_sector_size as u64; + if !(buf.len() as u64).is_multiple_of(sector_size) { + return Err(IoError::new( + IoErrorKind::InvalidInput, + format!( + "Read buffer length {} is not a multiple of the {sector_size}-byte logical sector size", + buf.len() + ), + )); + } + let sector_count = buf.len() as u64 / sector_size; + let sector_index = self.current_offset / sector_size; let result = io::read( &self.aligned, @@ -129,8 +139,18 @@ impl Write for Vhdx { /// Wrapper function to satisfy Write trait implementation for VHDx disk. /// Convert the offset to sector index and buffer length to sector count. fn write(&mut self, buf: &[u8]) -> IoResult { - let sector_count = (buf.len() as u64).div_ceil(self.disk_spec.logical_sector_size as u64); - let sector_index = self.current_offset / self.disk_spec.logical_sector_size as u64; + let sector_size = self.disk_spec.logical_sector_size as u64; + if !(buf.len() as u64).is_multiple_of(sector_size) { + return Err(IoError::new( + IoErrorKind::InvalidInput, + format!( + "Write buffer length {} is not a multiple of the {sector_size}-byte logical sector size", + buf.len() + ), + )); + } + let sector_count = buf.len() as u64 / sector_size; + let sector_index = self.current_offset / sector_size; if self.first_write { self.first_write = false; @@ -305,4 +325,42 @@ mod tests { assert_eq!(vhdx.read(&mut readback).unwrap(), readback.len()); assert_eq!(readback, data); } + + #[test] + fn read_misaligned_buffer_is_rejected() { + let Some(tf) = create_dynamic_vhdx(16) else { + eprintln!("skipping read_misaligned_buffer_is_rejected: qemu-img unavailable"); + return; + }; + + let file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(tf.as_path()) + .unwrap(); + let mut vhdx = Vhdx::new(file, false).unwrap(); + + let mut buf = vec![0u8; vhdx.disk_spec.logical_sector_size as usize - 1]; + let err = vhdx.read(&mut buf).unwrap_err(); + assert_eq!(err.kind(), IoErrorKind::InvalidInput); + } + + #[test] + fn write_misaligned_buffer_is_rejected() { + let Some(tf) = create_dynamic_vhdx(16) else { + eprintln!("skipping write_misaligned_buffer_is_rejected: qemu-img unavailable"); + return; + }; + + let file = fs::OpenOptions::new() + .read(true) + .write(true) + .open(tf.as_path()) + .unwrap(); + let mut vhdx = Vhdx::new(file, false).unwrap(); + + let buf = vec![0u8; vhdx.disk_spec.logical_sector_size as usize - 1]; + let err = vhdx.write(&buf).unwrap_err(); + assert_eq!(err.kind(), IoErrorKind::InvalidInput); + } }