Skip to main content

lamfold_iso/
el_torito.rs

1//! El Torito boot-catalog parsing — locating the embedded **UEFI** boot image.
2//!
3//! This is what boot-from-ISO needs: a downloaded `.iso` exposes its UEFI boot
4//! loader through an El Torito catalog, and LamBoot reads that image's byte
5//! range (then chainloads it as the embedded FAT ESP, `the boot-from-ISO design notes`
6//! path B). The catalog is a side-channel outside the filesystem surface — it
7//! has no directory/inode analogue.
8//!
9//! Clean-roomed from the public El Torito v1.0 specification.
10
11/// Platform id for UEFI El Torito entries (the 0xEF we want; 0x00 = x86 BIOS).
12const PLATFORM_UEFI: u8 = 0xEF;
13const BOOTABLE: u8 = 0x88;
14
15/// The El Torito UEFI boot image as a byte-range on the optical source. The
16/// range is `lba * 2048 .. + sectors * 512` (the LBA is a 2048-byte logical
17/// block; the sector count is in 512-byte virtual sectors, per the spec).
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct UefiImage {
20    pub lba: u32,
21    pub sectors: u32,
22}
23
24/// Parse a boot catalog (one 2048-byte sector is plenty for the common case) and
25/// return the first UEFI boot image, or `None` if there is no UEFI entry.
26///
27/// Handles both layouts real tools emit: a validation entry already on platform
28/// 0xEF (the default/initial entry is then the UEFI image), and a BIOS default
29/// entry followed by a 0xEF section header + section entries.
30pub fn parse_catalog(cat: &[u8]) -> Option<UefiImage> {
31    if cat.len() < 64 {
32        return None;
33    }
34    // Validation Entry (offset 0): header_id 0x01, platform @1, key 0x55AA @30..32.
35    if cat[0] != 0x01 || cat[30] != 0x55 || cat[31] != 0xAA {
36        return None;
37    }
38    if cat[1] == PLATFORM_UEFI {
39        // The Initial/Default Entry (offset 32) is the UEFI image.
40        return parse_entry(&cat[32..64]);
41    }
42    // Otherwise walk Section Headers (offset 64) for a 0xEF platform.
43    let mut off = 64;
44    while off + 32 <= cat.len() {
45        let hdr = &cat[off..off + 32];
46        let indicator = hdr[0]; // 0x90 = more headers follow, 0x91 = final header
47        if indicator != 0x90 && indicator != 0x91 {
48            break;
49        }
50        let platform = hdr[1];
51        let n_entries = u16::from_le_bytes([hdr[2], hdr[3]]) as usize;
52        off += 32;
53        if platform == PLATFORM_UEFI {
54            for _ in 0..n_entries {
55                if off + 32 > cat.len() {
56                    break;
57                }
58                if let Some(img) = parse_entry(&cat[off..off + 32]) {
59                    return Some(img);
60                }
61                off += 32;
62            }
63        } else {
64            off = off.saturating_add(n_entries.saturating_mul(32));
65        }
66        if indicator == 0x91 {
67            break;
68        }
69    }
70    None
71}
72
73/// Parse a 32-byte Section/Default Entry: boot_indicator @0, sector_count (u16
74/// LE) @6, load_rba (u32 LE) @8.
75fn parse_entry(e: &[u8]) -> Option<UefiImage> {
76    if e.len() < 12 || e[0] != BOOTABLE {
77        return None;
78    }
79    let sectors = u32::from(u16::from_le_bytes([e[6], e[7]]));
80    let lba = u32::from_le_bytes([e[8], e[9], e[10], e[11]]);
81    Some(UefiImage { lba, sectors })
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn parses_validation_uefi_default_entry() {
90        // The exact shape `xorrisofs -e ... -no-emul-boot` emits (see the et.iso
91        // fixture): validation entry platform 0xEF, default entry = the image.
92        let mut cat = [0u8; 96];
93        cat[0] = 0x01; // header id
94        cat[1] = 0xEF; // platform UEFI
95        cat[30] = 0x55;
96        cat[31] = 0xAA; // key
97        cat[32] = 0x88; // bootable
98        cat[38] = 0x04; // sector_count LE = 4
99        cat[40] = 0x22; // load_rba LE = 0x22 = 34
100        assert_eq!(
101            parse_catalog(&cat),
102            Some(UefiImage {
103                lba: 34,
104                sectors: 4
105            })
106        );
107    }
108
109    #[test]
110    fn finds_uefi_section_after_bios_default() {
111        let mut cat = [0u8; 128];
112        cat[0] = 0x01;
113        cat[1] = 0x00; // validation platform = BIOS
114        cat[30] = 0x55;
115        cat[31] = 0xAA;
116        cat[32] = 0x88; // a BIOS default entry (ignored)
117                        // Section header at offset 64: final (0x91), platform UEFI, 1 entry
118        cat[64] = 0x91;
119        cat[65] = 0xEF;
120        cat[66] = 0x01;
121        // Section entry at offset 96
122        cat[96] = 0x88;
123        cat[96 + 6] = 0x08; // 8 sectors
124        cat[96 + 8] = 0x40; // lba 0x40 = 64
125        assert_eq!(
126            parse_catalog(&cat),
127            Some(UefiImage {
128                lba: 64,
129                sectors: 8
130            })
131        );
132    }
133
134    #[test]
135    fn no_uefi_entry_returns_none() {
136        let mut cat = [0u8; 64];
137        cat[0] = 0x01;
138        cat[1] = 0x00;
139        cat[30] = 0x55;
140        cat[31] = 0xAA;
141        assert_eq!(parse_catalog(&cat), None);
142    }
143}