Skip to main content

vhdx/
region.rs

1use crate::bytes::{le_u32, le_u64};
2use crate::error::{Result, VhdxError};
3use crate::header::crc32c;
4
5pub const REGION_TABLE_SIGNATURE: &[u8; 4] = b"regi";
6pub const REGION_ENTRY_SIZE: usize = 32;
7/// Byte size of a region table block (CRC covers the full 64 KB).
8pub const REGION_TABLE_CRC_COVERAGE: usize = 65536;
9/// 1 MiB — the alignment granularity used throughout the VHDX format.
10pub const MB: u64 = 0x0010_0000;
11
12/// GUID bytes for the BAT region (MS-VHDX §2.3.4.1).
13pub const BAT_GUID: [u8; 16] = [
14    0x66, 0x77, 0xC2, 0x2D, 0x23, 0xF6, 0x00, 0x42, 0x9D, 0x64, 0x11, 0x5E, 0x9B, 0xFD, 0x4A, 0x08,
15];
16
17/// GUID bytes for the Metadata region (MS-VHDX §2.3.4.2).
18pub const METADATA_GUID: [u8; 16] = [
19    0x06, 0xA2, 0x7C, 0x8B, 0x90, 0x47, 0x9A, 0x4B, 0xB8, 0xFE, 0x57, 0x5F, 0x05, 0x0F, 0x88, 0x6E,
20];
21
22#[derive(Debug, Clone)]
23pub struct RegionEntry {
24    #[allow(dead_code)]
25    pub guid: [u8; 16],
26    pub file_offset: u64,
27    pub length: u32,
28}
29
30#[derive(Debug, Clone)]
31pub struct RegionTable {
32    pub bat: RegionEntry,
33    pub metadata: RegionEntry,
34}
35
36/// Maximum number of region table entries we will process.
37///
38/// The spec-defined region table size is 64 KB; with 32-byte entries that
39/// gives at most (65536 - 16) / 32 = 2047 entries. Cap at 2048 to prevent
40/// a crafted `entry_count = u32::MAX` from iterating over a large file.
41const REGION_ENTRY_COUNT_MAX: usize = 2048;
42
43/// Parse a region table from the slice `data` at byte `offset`.
44///
45/// `container_len` is the size of the WHOLE container (not the slice), used to
46/// range-check each region's `file_offset + length`. The bounded reader parses
47/// the region table from a small 1 MB prefix while the regions themselves live
48/// further into a multi-gigabyte image, so the bound must be the container size,
49/// never `data.len()`.
50pub fn parse_region_table(data: &[u8], offset: usize, container_len: u64) -> Result<RegionTable> {
51    if data.len() < offset + 16 {
52        return Err(VhdxError::InvalidRegionTable);
53    }
54    let slice = &data[offset..];
55    if &slice[0..4] != REGION_TABLE_SIGNATURE {
56        return Err(VhdxError::InvalidRegionTable);
57    }
58    let stored_crc = le_u32(slice, 4);
59    let mut buf = slice[..65536.min(slice.len())].to_vec();
60    buf.resize(65536, 0);
61    buf[4..8].fill(0);
62    if crc32c(&buf) != stored_crc {
63        return Err(VhdxError::InvalidRegionTable);
64    }
65    let entry_count = (le_u32(slice, 8) as usize).min(REGION_ENTRY_COUNT_MAX);
66    let mut bat: Option<RegionEntry> = None;
67    let mut metadata: Option<RegionEntry> = None;
68    for i in 0..entry_count {
69        let base = 16 + i * REGION_ENTRY_SIZE;
70        if base + REGION_ENTRY_SIZE > slice.len() {
71            break;
72        }
73        let mut guid = [0u8; 16];
74        guid.copy_from_slice(&slice[base..base + 16]);
75        let file_offset = le_u64(slice, base + 16);
76        let length = le_u32(slice, base + 24);
77        let region_end = file_offset
78            .checked_add(u64::from(length))
79            .ok_or(VhdxError::OffsetOutOfBounds)?;
80        if region_end > container_len {
81            return Err(VhdxError::OffsetOutOfBounds);
82        }
83        let entry = RegionEntry {
84            guid,
85            file_offset,
86            length,
87        };
88        if guid == BAT_GUID {
89            bat = Some(entry);
90        } else if guid == METADATA_GUID {
91            metadata = Some(entry);
92        }
93    }
94    Ok(RegionTable {
95        bat: bat.ok_or(VhdxError::BatRegionMissing)?,
96        metadata: metadata.ok_or(VhdxError::MetadataRegionMissing)?,
97    })
98}