Skip to main content

gpt_forensic/
header.rs

1//! GPT header (LBA 1 / backup) parsing and self-CRC validation.
2//!
3//! The header is 92 bytes (the spec minimum) at the start of its sector. Its own
4//! CRC-32 is computed over the first `header_size` bytes with the CRC field
5//! (offset 16..20) zeroed — a mismatch indicates corruption or tampering.
6
7use crate::crc32;
8use crate::guid::Guid;
9use crate::Error;
10
11/// The 8-byte GPT header signature.
12pub const SIGNATURE: &[u8; 8] = b"EFI PART";
13/// Minimum (and spec-standard) header size in bytes.
14pub const MIN_HEADER_SIZE: u32 = 92;
15/// Byte offset of the header's own CRC-32 field.
16const HEADER_CRC_OFFSET: usize = 16;
17
18/// A parsed GPT header.
19#[derive(Debug, Clone, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct GptHeader {
22    /// Header revision (e.g. `0x0001_0000` for v1.0).
23    pub revision: u32,
24    /// Header size in bytes (spec value: 92).
25    pub header_size: u32,
26    /// The CRC-32 stored in the header.
27    pub header_crc32: u32,
28    /// `true` when the stored CRC matches a recomputed CRC over the header.
29    pub header_crc_valid: bool,
30    /// LBA of this header (1 for primary; last LBA for backup).
31    pub my_lba: u64,
32    /// LBA of the other (backup/primary) header.
33    pub alternate_lba: u64,
34    /// First LBA usable by partitions.
35    pub first_usable_lba: u64,
36    /// Last LBA usable by partitions.
37    pub last_usable_lba: u64,
38    /// Disk GUID.
39    pub disk_guid: Guid,
40    /// LBA where the partition entry array begins.
41    pub partition_entry_lba: u64,
42    /// Number of entries in the partition array.
43    pub num_partition_entries: u32,
44    /// Size of each partition entry, in bytes (spec value: 128).
45    pub partition_entry_size: u32,
46    /// CRC-32 of the partition entry array.
47    pub partition_array_crc32: u32,
48}
49
50fn u32_le(b: &[u8], off: usize) -> u32 {
51    u32::from_le_bytes([b[off], b[off + 1], b[off + 2], b[off + 3]])
52}
53fn u64_le(b: &[u8], off: usize) -> u64 {
54    let mut a = [0u8; 8];
55    a.copy_from_slice(&b[off..off + 8]);
56    u64::from_le_bytes(a)
57}
58
59impl GptHeader {
60    /// Parse a GPT header from the start of `sector`.
61    ///
62    /// # Errors
63    /// [`Error::TooShort`] if `sector` is under 92 bytes; [`Error::BadSignature`]
64    /// if it does not begin with `"EFI PART"`.
65    pub fn parse(sector: &[u8]) -> Result<GptHeader, Error> {
66        if sector.len() < MIN_HEADER_SIZE as usize {
67            return Err(Error::TooShort {
68                need: MIN_HEADER_SIZE as usize,
69                got: sector.len(),
70            });
71        }
72        if &sector[0..8] != SIGNATURE {
73            return Err(Error::BadSignature);
74        }
75
76        let header_size = u32_le(sector, 12);
77        let header_crc32 = u32_le(sector, 16);
78        let header_crc_valid = compute_header_crc(sector, header_size)
79            .is_some_and(|computed| computed == header_crc32);
80
81        let mut disk_guid = [0u8; 16];
82        disk_guid.copy_from_slice(&sector[56..72]);
83
84        Ok(GptHeader {
85            revision: u32_le(sector, 8),
86            header_size,
87            header_crc32,
88            header_crc_valid,
89            my_lba: u64_le(sector, 24),
90            alternate_lba: u64_le(sector, 32),
91            first_usable_lba: u64_le(sector, 40),
92            last_usable_lba: u64_le(sector, 48),
93            disk_guid: Guid(disk_guid),
94            partition_entry_lba: u64_le(sector, 72),
95            num_partition_entries: u32_le(sector, 80),
96            partition_entry_size: u32_le(sector, 84),
97            partition_array_crc32: u32_le(sector, 88),
98        })
99    }
100}
101
102/// Recompute the header CRC over `header_size` bytes with the CRC field zeroed.
103/// Returns `None` if `header_size` is implausible (out of `[92, sector.len()]`).
104fn compute_header_crc(sector: &[u8], header_size: u32) -> Option<u32> {
105    let size = header_size as usize;
106    if header_size < MIN_HEADER_SIZE || size > sector.len() {
107        return None;
108    }
109    let mut buf = sector[..size].to_vec();
110    buf[HEADER_CRC_OFFSET..HEADER_CRC_OFFSET + 4].fill(0);
111    Some(crc32::checksum(&buf))
112}