Skip to main content

vhdx/
region.rs

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