Nothing to see here, move along
1use crate::static_vec::StaticVec;
2use zerocopy::{FromBytes, Immutable, KnownLayout};
3
4const ELF_MAGIC: [u8; 4] = [0x7f, b'E', b'L', b'F'];
5const PT_LOAD: u32 = 1;
6const MAX_LOAD_SEGMENTS: usize = 8;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum ElfError {
10 Truncated,
11 BadMagic,
12 UnsupportedFormat,
13 InvalidPhdrSize,
14 TooManySegments,
15 MalformedSegment,
16}
17
18fn read_struct<T: FromBytes + KnownLayout + Immutable>(data: &[u8], offset: usize) -> Option<T> {
19 let size = core::mem::size_of::<T>();
20 let end = offset.checked_add(size)?;
21 T::read_from_bytes(data.get(offset..end)?).ok()
22}
23
24#[repr(C)]
25#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)]
26pub struct Elf64Header {
27 pub e_ident: [u8; 16],
28 pub e_type: u16,
29 pub e_machine: u16,
30 pub e_version: u32,
31 pub e_entry: u64,
32 pub e_phoff: u64,
33 pub e_shoff: u64,
34 pub e_flags: u32,
35 pub e_ehsize: u16,
36 pub e_phentsize: u16,
37 pub e_phnum: u16,
38 pub e_shentsize: u16,
39 pub e_shnum: u16,
40 pub e_shstrndx: u16,
41}
42
43#[repr(C)]
44#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)]
45pub struct Elf64Phdr {
46 pub p_type: u32,
47 pub p_flags: u32,
48 pub p_offset: u64,
49 pub p_vaddr: u64,
50 pub p_paddr: u64,
51 pub p_filesz: u64,
52 pub p_memsz: u64,
53 pub p_align: u64,
54}
55
56#[derive(Debug)]
57pub struct ElfInfo {
58 pub entry: u64,
59 pub segments: StaticVec<LoadSegment, MAX_LOAD_SEGMENTS>,
60}
61
62#[derive(Debug, Clone, Copy)]
63pub struct LoadSegment {
64 pub vaddr: u64,
65 pub file_offset: u64,
66 pub filesz: u64,
67 pub memsz: u64,
68 pub writable: bool,
69 pub executable: bool,
70}
71
72pub fn parse(data: &[u8]) -> Result<ElfInfo, ElfError> {
73 let header: Elf64Header = read_struct(data, 0).ok_or(ElfError::Truncated)?;
74
75 if header.e_ident[0..4] != ELF_MAGIC {
76 return Err(ElfError::BadMagic);
77 }
78
79 if header.e_ident[4] != 2 || header.e_ident[5] != 1 || header.e_machine != 0x3E {
80 return Err(ElfError::UnsupportedFormat);
81 }
82
83 if (header.e_phentsize as usize) < core::mem::size_of::<Elf64Phdr>() {
84 return Err(ElfError::InvalidPhdrSize);
85 }
86
87 let mut segments: StaticVec<LoadSegment, MAX_LOAD_SEGMENTS> = StaticVec::new();
88
89 let phdr_base = header.e_phoff as usize;
90 let phdr_size = header.e_phentsize as usize;
91
92 (0..header.e_phnum as usize)
93 .filter_map(|i| {
94 let offset = phdr_base.checked_add(i.checked_mul(phdr_size)?)?;
95 let phdr: Elf64Phdr = read_struct(data, offset)?;
96 let valid = phdr.p_type == PT_LOAD
97 && phdr.p_memsz > 0
98 && phdr.p_memsz >= phdr.p_filesz
99 && phdr
100 .p_offset
101 .checked_add(phdr.p_filesz)
102 .is_some_and(|end| end <= data.len() as u64);
103 if valid {
104 Some(LoadSegment {
105 vaddr: phdr.p_vaddr,
106 file_offset: phdr.p_offset,
107 filesz: phdr.p_filesz,
108 memsz: phdr.p_memsz,
109 writable: (phdr.p_flags & 2) != 0,
110 executable: (phdr.p_flags & 1) != 0,
111 })
112 } else {
113 None
114 }
115 })
116 .try_fold(&mut segments, |segs, seg| match segs.push(seg) {
117 Ok(()) => Ok(segs),
118 Err(_) => Err(ElfError::TooManySegments),
119 })?;
120
121 Ok(ElfInfo {
122 entry: header.e_entry,
123 segments,
124 })
125}
126
127#[cfg(kani)]
128mod kani_proofs {
129 use super::*;
130
131 #[kani::proof]
132 fn parse_rejects_empty() {
133 assert!(parse(&[]).is_err());
134 }
135
136 #[kani::proof]
137 fn read_struct_rejects_truncated_header() {
138 let len: usize = kani::any();
139 kani::assume(len < core::mem::size_of::<Elf64Header>());
140 let data = [0u8; 63];
141 let result: Option<Elf64Header> = read_struct(&data[..len], 0);
142 assert!(result.is_none());
143 }
144
145 #[kani::proof]
146 fn read_struct_bounds_check() {
147 let data: [u8; 16] = kani::any();
148 let offset: usize = kani::any();
149 kani::assume(offset <= 20);
150 let result: Option<u64> = read_struct(&data, offset);
151 if offset.checked_add(8).is_none_or(|end| end > 16) {
152 assert!(result.is_none());
153 }
154 }
155
156 #[kani::proof]
157 #[kani::unwind(10)]
158 fn phdr_offset_arithmetic_no_overflow() {
159 let base: usize = kani::any();
160 let entry_size: usize = kani::any();
161 let count: usize = kani::any();
162 kani::assume(count <= 8);
163 kani::assume(entry_size >= core::mem::size_of::<Elf64Phdr>());
164 kani::assume(entry_size <= 256);
165
166 (0..count).for_each(|i| {
167 let product = i.checked_mul(entry_size);
168 let offset = product.and_then(|p| base.checked_add(p));
169 assert!(product.is_none() || offset.is_some() || base > usize::MAX - product.unwrap());
170 });
171 }
172
173 #[kani::proof]
174 fn segment_file_bounds_check() {
175 let data_len: u64 = kani::any();
176 let p_offset: u64 = kani::any();
177 let p_filesz: u64 = kani::any();
178
179 kani::assume(data_len <= 0x1_0000);
180
181 let in_bounds = p_offset
182 .checked_add(p_filesz)
183 .is_some_and(|end| end <= data_len);
184
185 if in_bounds {
186 assert!(p_offset <= data_len);
187 assert!(p_filesz <= data_len);
188 }
189 }
190
191 #[kani::proof]
192 fn segment_memsz_ge_filesz_check() {
193 let p_memsz: u64 = kani::any();
194 let p_filesz: u64 = kani::any();
195
196 let valid = p_memsz > 0 && p_memsz >= p_filesz;
197 if valid {
198 assert!(p_memsz >= p_filesz);
199 }
200 }
201}