block: vhdx: reject overflowing region table entries

The region table overlap fix in RegionInfo::new computes each entry's
end offset as `file_offset + length`. Both values are taken verbatim
from the image, so a crafted or corrupt VHDX can set a file offset near
u64::MAX and make that addition wrap. A wrapped end offset compares as a
small value, which can slip a genuinely overlapping region past the
half-open interval check that #8483 added.

Use checked_add for the end offset and return a new RegionEntryOverflow
error when it wraps, so a malformed entry is rejected instead of being
folded into a valid-looking range. The computed end is now reused for
the region_entries map so the bound is only calculated once. Add a
regression test for a wrapping entry.

Signed-off-by: Sayed Kaif <metsw24@gmail.com>
This commit is contained in:
Sayed Kaif
2026-07-01 16:57:35 +05:30
committed by Rob Bradford
parent 4b120c3702
commit 6882276e2d

View File

@@ -72,6 +72,8 @@ pub enum VhdxHeaderError {
ReadRegionTableHeader(#[source] io::Error),
#[error("Failed to read region entries")]
RegionEntryCollectionFailed,
#[error("Region entry file offset ({0}) and length ({1}) overflow u64")]
RegionEntryOverflow(u64 /* start */, usize /* length */),
#[error("Overlapping regions found")]
RegionOverlap,
#[error("Reserved region has non-zero value")]
@@ -264,7 +266,9 @@ impl RegionInfo {
offset += size_of::<RegionTableEntry>();
let start = entry.file_offset;
let end = start + entry.length as u64;
let end = start.checked_add(entry.length as u64).ok_or(
VhdxHeaderError::RegionEntryOverflow(start, entry.length as usize),
)?;
for (region_ent_start, region_ent_end) in region_entries.iter() {
if ranges_overlap(start, end, *region_ent_start, *region_ent_end) {
@@ -272,7 +276,7 @@ impl RegionInfo {
}
}
region_entries.insert(entry.file_offset, entry.file_offset + entry.length as u64);
region_entries.insert(start, end);
if entry.guid == BAT_GUID {
if bat_entry.is_none() {
@@ -560,4 +564,26 @@ mod tests {
"expected RegionOverlap for an overlapping region table"
);
}
#[test]
fn test_region_info_rejects_overflowing_region() {
// A region whose file offset plus length wraps past u64::MAX must be
// rejected rather than silently producing a small end offset that
// could mask a genuine overlap.
let region_start = REGION_TABLE_1_START;
let entries_at = region_start + size_of::<RegionTableHeader>() as u64;
let temp = TempFile::new().unwrap();
let f = temp.into_file();
f.set_len(entries_at + 64 * 1024).unwrap();
f.write_all_at(&region_entry(BAT_GUID, u64::MAX, 0x1000), entries_at)
.unwrap();
let af = AlignedFile::new(f, false);
let res = RegionInfo::new(&af, region_start, 1);
assert!(
matches!(res, Err(VhdxHeaderError::RegionEntryOverflow(..))),
"expected RegionEntryOverflow for a wrapping region entry"
);
}
}