1use anyhow::{bail, Context, Result};
7use goblin::elf::Elf;
8
9#[derive(Clone)]
10pub struct Segment {
11 pub vaddr: u64,
12 pub bytes: Vec<u8>,
13 pub exec: bool,
14 pub write: bool,
15}
16
17#[derive(Clone, Debug)]
18pub struct Func {
19 pub name: Option<String>,
20 pub entry: u64,
21 pub size: u64,
22 pub source: FuncSource,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
27pub enum FuncSource {
28 Symtab,
29 DynSym,
30 EhFrame,
31}
32
33pub struct Image {
34 pub segments: Vec<Segment>,
35 pub entry: u64,
36 pub is_pie: bool,
37}
38
39impl Image {
40 pub fn code_at(&self, vaddr: u64, len: usize) -> Option<&[u8]> {
44 let len = len as u64;
45 for s in &self.segments {
46 if vaddr < s.vaddr {
47 continue;
48 }
49 let off = vaddr - s.vaddr; let seg_len = s.bytes.len() as u64;
51 if off.checked_add(len).is_none_or(|end| end > seg_len) {
53 continue;
54 }
55 let off = off as usize;
56 return Some(&s.bytes[off..off + len as usize]);
57 }
58 None
59 }
60}
61
62pub struct Loaded {
63 pub image: Image,
64 pub funcs: Vec<Func>,
65}
66
67const MAX_SEG_MEM: u64 = 1 << 30; const MAX_TOTAL_MEM: u64 = 1 << 31; pub fn load(bytes: &[u8]) -> Result<Loaded> {
80 let elf = Elf::parse(bytes).context("not a valid elf")?;
81 if elf.header.e_machine != goblin::elf::header::EM_X86_64 {
82 bail!(
83 "only x86-64 is supported in this version (got e_machine {})",
84 elf.header.e_machine
85 );
86 }
87
88 let mut plans: Vec<(usize, usize, usize, u64, bool, bool)> = Vec::new();
97 let mut total_mem: u64 = 0;
98 for ph in &elf.program_headers {
99 if ph.p_type != goblin::elf::program_header::PT_LOAD {
100 continue;
101 }
102 if ph.p_memsz > MAX_SEG_MEM {
104 bail!(
105 "PT_LOAD p_memsz {} over {}-byte limit, refusing",
106 ph.p_memsz,
107 MAX_SEG_MEM
108 );
109 }
110 if ph.p_vaddr.checked_add(ph.p_memsz).is_none() {
113 bail!("PT_LOAD vaddr {:#x} + memsz overflows", ph.p_vaddr);
114 }
115
116 let start = usize::try_from(ph.p_offset)
120 .unwrap_or(usize::MAX)
121 .min(bytes.len());
122 let fsz = usize::try_from(ph.p_filesz).unwrap_or(usize::MAX);
123 let end = start.saturating_add(fsz).min(bytes.len());
124 let memsz = usize::try_from(ph.p_memsz).unwrap_or(usize::MAX);
126
127 let alloc_len = (end - start).max(memsz); total_mem = total_mem.saturating_add(alloc_len as u64);
129 if total_mem > MAX_TOTAL_MEM {
130 bail!("total PT_LOAD memory over {}-byte limit", MAX_TOTAL_MEM);
131 }
132 plans.push((
133 start,
134 end,
135 memsz,
136 ph.p_vaddr,
137 ph.is_executable(),
138 ph.is_write(),
139 ));
140 }
141 if plans.is_empty() {
142 bail!("no PT_LOAD segments");
143 }
144
145 let mut segments = Vec::with_capacity(plans.len());
147 for (start, end, memsz, vaddr, exec, write) in plans {
148 let mut data = Vec::with_capacity((end - start).max(memsz));
149 data.extend_from_slice(&bytes[start..end]);
150 if memsz > data.len() {
152 data.resize(memsz, 0);
153 }
154 segments.push(Segment {
155 vaddr,
156 bytes: data,
157 exec,
158 write,
159 });
160 }
161
162 let is_pie = elf.header.e_type == goblin::elf::header::ET_DYN;
163
164 let mut funcs = discover(&elf, bytes)?;
165 funcs.sort_by(|a, b| {
167 a.entry
168 .cmp(&b.entry)
169 .then(b.name.is_some().cmp(&a.name.is_some()))
170 });
171 funcs.dedup_by_key(|f| f.entry);
172
173 Ok(Loaded {
174 image: Image {
175 segments,
176 entry: elf.header.e_entry,
177 is_pie,
178 },
179 funcs,
180 })
181}
182
183const MAX_NAME: usize = 4096;
190const MAX_FUNCS: usize = 1_000_000;
191
192fn clamp_name(s: &str) -> String {
193 if s.len() <= MAX_NAME {
194 return s.to_string();
195 }
196 let mut end = MAX_NAME;
197 while end > 0 && !s.is_char_boundary(end) {
198 end -= 1;
199 }
200 s[..end].to_string()
201}
202
203fn discover(elf: &Elf, raw: &[u8]) -> Result<Vec<Func>> {
204 let mut out = Vec::new();
205
206 for (sym, src) in elf
207 .syms
208 .iter()
209 .map(|s| (s, FuncSource::Symtab))
210 .chain(elf.dynsyms.iter().map(|s| (s, FuncSource::DynSym)))
211 {
212 if out.len() >= MAX_FUNCS {
213 break; }
215 if sym.st_type() != goblin::elf::sym::STT_FUNC {
216 continue;
217 }
218 if sym.st_value == 0 || sym.st_size == 0 {
219 continue; }
221 let name = match src {
222 FuncSource::Symtab => elf.strtab.get_at(sym.st_name),
223 _ => elf.dynstrtab.get_at(sym.st_name),
224 }
225 .map(clamp_name)
226 .filter(|s| !s.is_empty());
227 out.push(Func {
228 name,
229 entry: sym.st_value,
230 size: sym.st_size,
231 source: src,
232 });
233 }
234
235 if out.is_empty() {
237 if let Some(mut fdes) = eh_frame_funcs(elf, raw) {
238 out.append(&mut fdes);
239 }
240 }
241
242 Ok(out)
243}
244
245fn eh_frame_funcs(elf: &Elf, raw: &[u8]) -> Option<Vec<Func>> {
247 use gimli::{BaseAddresses, CieOrFde, EhFrame, LittleEndian, UnwindSection};
248
249 let sh = elf
250 .section_headers
251 .iter()
252 .find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(".eh_frame"))?;
253 let start = usize::try_from(sh.sh_offset).ok()?;
257 let size = usize::try_from(sh.sh_size).ok()?;
258 let end = start.checked_add(size)?;
259 let data = raw.get(start..end)?;
260
261 let eh = EhFrame::new(data, LittleEndian);
262 let bases = BaseAddresses::default().set_eh_frame(sh.sh_addr);
263
264 let mut entries = eh.entries(&bases);
265 let mut out = Vec::new();
266 loop {
267 match entries.next() {
268 Ok(Some(CieOrFde::Fde(partial))) => {
269 if let Ok(fde) = partial.parse(EhFrame::cie_from_offset) {
270 let entry = fde.initial_address();
271 let size = fde.len();
272 if size > 0 {
273 out.push(Func {
274 name: None,
275 entry,
276 size,
277 source: FuncSource::EhFrame,
278 });
279 }
280 }
281 }
282 Ok(Some(CieOrFde::Cie(_))) => {}
283 Ok(None) => break,
284 Err(_) => break,
285 }
286 }
287 if out.is_empty() {
288 None
289 } else {
290 Some(out)
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn garbage_input_errors_not_panics() {
300 assert!(load(b"").is_err());
301 assert!(load(b"not an elf at all, just text here").is_err());
302 let mut junk = vec![0x7f, b'E', b'L', b'F'];
304 junk.extend(std::iter::repeat_n(0x41u8, 400));
305 let _ = load(&junk); }
307
308 #[test]
309 fn truncated_header_does_not_panic() {
310 let mut hdr = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0];
311 hdr.extend(std::iter::repeat_n(0u8, 48));
312 let _ = load(&hdr);
313 }
314
315 fn craft_elf(p_offset: u64, p_vaddr: u64, p_filesz: u64, p_memsz: u64) -> Vec<u8> {
319 let mut e = vec![0u8; 64 + 56];
320 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
321 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
325 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
326 let put32 =
327 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
328 let put64 =
329 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
330 put16(&mut e, 16, 2); put16(&mut e, 18, 62); put32(&mut e, 20, 1); put64(&mut e, 32, 64); put16(&mut e, 52, 64); put16(&mut e, 54, 56); put16(&mut e, 56, 1); let ph = 64;
339 put32(&mut e, ph, 1); put32(&mut e, ph + 4, 5); put64(&mut e, ph + 8, p_offset);
342 put64(&mut e, ph + 16, p_vaddr);
343 put64(&mut e, ph + 32, p_filesz);
344 put64(&mut e, ph + 40, p_memsz);
345 put64(&mut e, ph + 48, 0x1000); e
347 }
348
349 #[test]
350 fn huge_memsz_is_refused_not_allocated() {
351 let elf = craft_elf(0, 0x1000, 0, u64::MAX);
353 assert!(load(&elf).is_err());
354 let elf = craft_elf(0, 0x1000, 0, MAX_SEG_MEM + 1);
356 assert!(load(&elf).is_err());
357 }
358
359 #[test]
360 fn vaddr_plus_memsz_overflow_is_refused() {
361 let elf = craft_elf(0, u64::MAX - 16, 0, 4096);
362 assert!(load(&elf).is_err());
363 }
364
365 fn craft_elf_many_load(n: u16) -> Vec<u8> {
370 let phoff = 64usize;
371 let file_len = phoff + (n as usize) * 56;
372 let mut e = vec![0u8; file_len];
373 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
374 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
378 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
379 let put32 =
380 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
381 let put64 =
382 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
383 put16(&mut e, 16, 2); put16(&mut e, 18, 62); put32(&mut e, 20, 1);
386 put64(&mut e, 32, phoff as u64);
387 put16(&mut e, 52, 64);
388 put16(&mut e, 54, 56);
389 put16(&mut e, 56, n);
390 for i in 0..n as usize {
391 let ph = phoff + i * 56;
392 put32(&mut e, ph, 1); put32(&mut e, ph + 4, 4); put64(&mut e, ph + 8, 0); put64(&mut e, ph + 16, 0x1000 + i as u64 * 0x1000); put64(&mut e, ph + 32, file_len as u64); put64(&mut e, ph + 40, 0); put64(&mut e, ph + 48, 0x1000);
399 }
400 e
401 }
402
403 #[test]
404 fn overlapping_file_backed_segments_are_refused_not_allocated() {
405 let elf = craft_elf_many_load(20000);
408 let r = load(&elf);
409 assert!(
410 r.is_err(),
411 "expected the alloc-amplification bomb to be refused"
412 );
413 }
414
415 #[test]
416 fn offset_past_eof_does_not_panic() {
417 let elf = craft_elf(0xffff_0000, 0x1000, 32, 32);
419 let _ = load(&elf); }
421
422 #[test]
423 fn code_at_high_vaddr_no_overflow() {
424 let img = Image {
427 segments: vec![Segment {
428 vaddr: u64::MAX - 8,
429 bytes: vec![0u8; 8],
430 exec: true,
431 write: false,
432 }],
433 entry: 0,
434 is_pie: false,
435 };
436 assert!(img.code_at(u64::MAX - 4, 64).is_none());
437 assert!(img.code_at(u64::MAX, 16).is_none());
438 }
439}