Skip to main content

filevault/
read.rs

1//! Bounds-checked integer/byte readers over attacker-controllable image data.
2//!
3//! Every accessor returns a value (0 / empty on out-of-range) rather than
4//! panicking or indexing out of bounds — a length or offset field taken from
5//! the image can be arbitrary, so reads are validated against the slice length
6//! first (Paranoid Gatekeeper).
7
8/// Reads a little-endian `u16` at `off`, or `0` if the 2 bytes are out of range.
9#[must_use]
10pub fn le_u16(data: &[u8], off: usize) -> u16 {
11    match data.get(off..off + 2) {
12        Some(s) => u16::from_le_bytes([s[0], s[1]]),
13        None => 0,
14    }
15}
16
17/// Reads a little-endian `u32` at `off`, or `0` if the 4 bytes are out of range.
18#[must_use]
19pub fn le_u32(data: &[u8], off: usize) -> u32 {
20    match data.get(off..off + 4) {
21        Some(s) => u32::from_le_bytes([s[0], s[1], s[2], s[3]]),
22        None => 0,
23    }
24}
25
26/// Reads a little-endian `u64` at `off`, or `0` if the 8 bytes are out of range.
27#[must_use]
28pub fn le_u64(data: &[u8], off: usize) -> u64 {
29    match data.get(off..off + 8) {
30        Some(s) => u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]),
31        None => 0,
32    }
33}
34
35/// Reads a little-endian 6-byte block number at `off` (widened to `u64`), or
36/// `0` if out of range. CoreStorage stores physical-volume block numbers as
37/// 48-bit little-endian values.
38#[must_use]
39pub fn le_u48(data: &[u8], off: usize) -> u64 {
40    match data.get(off..off + 6) {
41        Some(s) => {
42            u64::from(s[0])
43                | (u64::from(s[1]) << 8)
44                | (u64::from(s[2]) << 16)
45                | (u64::from(s[3]) << 24)
46                | (u64::from(s[4]) << 32)
47                | (u64::from(s[5]) << 40)
48        }
49        None => 0,
50    }
51}
52
53/// Copies a fixed 16-byte array (a UUID / key half) at `off`, zero-filled if the
54/// range is out of bounds.
55#[must_use]
56pub fn bytes16(data: &[u8], off: usize) -> [u8; 16] {
57    let mut out = [0u8; 16];
58    if let Some(s) = data.get(off..off + 16) {
59        out.copy_from_slice(s);
60    }
61    out
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67
68    #[test]
69    fn le_readers_decode_in_range() {
70        let d = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08];
71        assert_eq!(le_u16(&d, 0), 0x0201);
72        assert_eq!(le_u32(&d, 0), 0x0403_0201);
73        assert_eq!(le_u64(&d, 0), 0x0807_0605_0403_0201);
74        assert_eq!(le_u48(&d, 0), 0x0605_0403_0201);
75    }
76
77    #[test]
78    fn le_readers_return_zero_out_of_range() {
79        let d = [0xffu8; 3];
80        assert_eq!(le_u16(&d, 2), 0);
81        assert_eq!(le_u32(&d, 0), 0);
82        assert_eq!(le_u64(&d, 0), 0);
83        assert_eq!(le_u48(&d, 0), 0);
84        assert_eq!(le_u16(&[], 0), 0);
85    }
86
87    #[test]
88    fn bytes16_copies_or_zero_fills() {
89        let mut d = [0u8; 20];
90        for (i, b) in d.iter_mut().enumerate() {
91            *b = i as u8;
92        }
93        assert_eq!(bytes16(&d, 0)[15], 15);
94        assert_eq!(bytes16(&d, 4)[0], 4);
95        // Out of range -> zeroed.
96        assert_eq!(bytes16(&d, 10), [0u8; 16]);
97        assert_eq!(bytes16(&[], 0), [0u8; 16]);
98    }
99}