Skip to main content

onelf_format/
elf.rs

1//! Minimal ELF probing shared by the packer and the runtime.
2//!
3//! Only what both sides need to locate a program header, kept in one place so
4//! a hardened parse cannot drift back into an unchecked duplicate.
5
6/// Locate the PT_INTERP program header in `data` (which must contain the ELF
7/// header and program-header table) and return the interpreter string's
8/// `(file offset, size-in-file)`. Little-endian 32- and 64-bit ELF; every
9/// read is bounds-checked against `data`. The interp bytes themselves may lie
10/// beyond `data` (the caller reads them, possibly re-reading the file).
11pub fn pt_interp_slot(data: &[u8]) -> Option<(usize, usize)> {
12    if data.len() < 64 || data[0..4] != *b"\x7fELF" {
13        return None;
14    }
15    let class = data[4];
16    let (e_phoff, e_phentsize, e_phnum) = match class {
17        2 => (
18            u64::from_le_bytes(data.get(32..40)?.try_into().ok()?) as usize,
19            u16::from_le_bytes(data.get(54..56)?.try_into().ok()?) as usize,
20            u16::from_le_bytes(data.get(56..58)?.try_into().ok()?) as usize,
21        ),
22        1 => (
23            u32::from_le_bytes(data.get(28..32)?.try_into().ok()?) as usize,
24            u16::from_le_bytes(data.get(42..44)?.try_into().ok()?) as usize,
25            u16::from_le_bytes(data.get(44..46)?.try_into().ok()?) as usize,
26        ),
27        _ => return None,
28    };
29    // Each program-header entry must be large enough to hold the fields we
30    // read below (p_offset / p_filesz); reject malformed tables up front.
31    let min_phentsize = if class == 2 { 56 } else { 32 };
32    if e_phentsize < min_phentsize {
33        return None;
34    }
35    for i in 0..e_phnum {
36        let off = e_phoff.checked_add(i.checked_mul(e_phentsize)?)?;
37        let end = off.checked_add(e_phentsize)?;
38        if end > data.len() {
39            break;
40        }
41        let p_type = u32::from_le_bytes(data.get(off..off + 4)?.try_into().ok()?);
42        if p_type != 3 {
43            continue;
44        }
45        return match class {
46            2 => Some((
47                u64::from_le_bytes(data.get(off + 8..off + 16)?.try_into().ok()?) as usize,
48                u64::from_le_bytes(data.get(off + 32..off + 40)?.try_into().ok()?) as usize,
49            )),
50            1 => Some((
51                u32::from_le_bytes(data.get(off + 4..off + 8)?.try_into().ok()?) as usize,
52                u32::from_le_bytes(data.get(off + 16..off + 20)?.try_into().ok()?) as usize,
53            )),
54            _ => None,
55        };
56    }
57    None
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    /// A 64-bit ELF whose single program header is a PT_INTERP pointing at
65    /// `interp`, laid out immediately after the header table.
66    fn elf64_with_interp(interp: &str) -> Vec<u8> {
67        let phoff = 64usize;
68        let phentsize = 56usize;
69        let interp_off = phoff + phentsize;
70        let mut v = vec![0u8; interp_off + interp.len() + 1];
71        v[0..4].copy_from_slice(b"\x7fELF");
72        v[4] = 2; // ELFCLASS64
73        v[32..40].copy_from_slice(&(phoff as u64).to_le_bytes());
74        v[54..56].copy_from_slice(&(phentsize as u16).to_le_bytes());
75        v[56..58].copy_from_slice(&1u16.to_le_bytes()); // e_phnum
76        v[phoff..phoff + 4].copy_from_slice(&3u32.to_le_bytes()); // PT_INTERP
77        v[phoff + 8..phoff + 16].copy_from_slice(&(interp_off as u64).to_le_bytes());
78        v[phoff + 32..phoff + 40].copy_from_slice(&((interp.len() + 1) as u64).to_le_bytes());
79        v[interp_off..interp_off + interp.len()].copy_from_slice(interp.as_bytes());
80        v
81    }
82
83    #[test]
84    fn locates_the_interp_slot() {
85        let name = "/lib64/ld-linux-x86-64.so.2";
86        let elf = elf64_with_interp(name);
87        let (off, sz) = pt_interp_slot(&elf).expect("slot");
88        assert_eq!(off, 120);
89        assert_eq!(sz, name.len() + 1);
90        assert_eq!(&elf[off..off + sz - 1], name.as_bytes());
91    }
92
93    #[test]
94    fn malformed_returns_none_without_panic() {
95        assert!(pt_interp_slot(b"not an elf").is_none());
96        assert!(pt_interp_slot(&[]).is_none());
97
98        // ELF magic but a program-header table pointing off the end.
99        let mut short = vec![0u8; 64];
100        short[0..4].copy_from_slice(b"\x7fELF");
101        short[4] = 2;
102        short[56..58].copy_from_slice(&9999u16.to_le_bytes());
103        short[32..40].copy_from_slice(&(1u64 << 40).to_le_bytes());
104        assert!(pt_interp_slot(&short).is_none());
105    }
106
107    #[test]
108    fn undersized_phentsize_is_rejected() {
109        // A table whose entries are too small to hold the fields read below
110        // would otherwise be walked with garbage offsets.
111        let mut elf = elf64_with_interp("/lib/ld.so");
112        elf[54..56].copy_from_slice(&8u16.to_le_bytes());
113        assert!(pt_interp_slot(&elf).is_none());
114    }
115}