block: vhdx: use logical sector size for block transfers - #8352
metsw24-max wants to merge 1 commit into
Conversation
|
@weltling could you please take a look? |
|
@metsw24-max Please follow the contribution guideline to add a Signed-off-by trailer. |
e47a129 to
33dd29a
Compare
|
Done. Amended the commit with a Signed-off-by trailer and force-pushed. |
weltling
left a comment
There was a problem hiding this comment.
LGTM.
free_bytes is how the loop advances, so it's more accurate than free_sectors * 512.
| [read_count..(read_count + (sector.free_sectors * SECTOR_SIZE) as usize)], | ||
| ) | ||
| .map_err(VhdxIoError::ReadSectorBlock)?; | ||
| f.read_exact(&mut buf[read_count..(read_count + sector.free_bytes as usize)]) |
There was a problem hiding this comment.
I think this is still wrong:
For a 4096-byte logical-sector image with a 512-byte virtio read (buf.len() == 512):
- mod.rs:103: sector_count = div_ceil(512, 4096) = 1
- Sector::new: free_sectors = 1, free_bytes = 1 * 4096 = 4096
- io.rs:117: buf[0..(0 + 4096)] on a 512-byte slice → panic (out of bounds)
There was a problem hiding this comment.
And I think there is a similar problem in mod.rs
There was a problem hiding this comment.
Thanks for the close read, @rbradford. My initial calculation was free_sectors * logical_sector_size, which dynamically matches what the loop commits to via read_count += free_bytes. The div_ceil in mod.rs seems preexisting and a correctness fix belongs there.
Thanks
There was a problem hiding this comment.
You're right. Once the transfer length was honest about the sector size, the round-up in mod.rs meant io::read could still slice past the end of the buffer, so the panic just moved out of the caller and into io.rs. The round-down on the offset had the same assumption baked in and would silently read the wrong sector.
I've made the conversion in mod.rs exact instead: read and write now reject requests whose offset or length isn't a multiple of the logical sector size with InvalidInput, which guarantees sector_count * logical_sector_size == buf.len() by the time io::read/io::write run, so the slice bounds can't be exceeded. This also covers 512-byte images, where a buffer length that wasn't a multiple of 512 could previously hit the same round-up overrun.
Supporting sub-sector requests properly would mean plumbing the intra-sector offset through io::read/io::write; I'd rather do that as a follow-up than grow this fix, but happy to take it on if you'd prefer 512-byte requests against 4K-sector images to work rather than fail cleanly.
read and write sized each transfer as free_sectors * 512, but the loop cursor advances by free_sectors * logical_sector_size, so a 4096-byte sector image moved only an eighth of every block and read could report more bytes than the destination buffer holds. Use the already-computed free_bytes at the three I/O sites. The byte-to-sector conversion in the Read/Write wrappers also assumed sector alignment: it rounded the count up and the offset down, so a sub-sector request (e.g. a 512-byte read on a 4096-byte sector image) made io::read slice past the end of the buffer, and an unaligned offset silently transferred the wrong sector. Reject requests whose offset or length is not a multiple of the logical sector size with InvalidInput, which guarantees sector_count * logical_sector_size == buf.len() by the time io::read and io::write run. Signed-off-by: metsw24-max <[email protected]>
33dd29a to
3441add
Compare
|
Is this change still needed in light of #8378 merging. |
|
I've lost track if this is still relevant - I don't think so? |
Wrong transfer length for VHDX images with 4K logical sectors
readandwritesize each block transfer asfree_sectors * 512, yet the loop cursor advances byfree_sectors * logical_sector_size, so a 4096-byte-sector image moves only an eighth of every block andreadcan return more bytes than the destination buffer holds (a 512-byte read then panics in the caller on&buf[..result]). Switched the three sites to the already-computedfree_bytes, which leaves 512-byte images byte-for-byte unchanged.The byte-to-sector conversion in the
Read/Writewrappers had the same assumption baked in: it rounded the sector count up and the offset down, so a sub-sector request madeio::readslice past the end of the buffer and an unaligned offset silently transferred the wrong sector. Requests whose offset or length isn't a multiple of the logical sector size are now rejected withInvalidInput, which guaranteessector_count * logical_sector_size == buf.len()by the timeio::read/io::writerun.