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 << 31; const MAX_TOTAL_MEM: u64 = 1 << 32; pub fn load(bytes: &[u8]) -> Result<Loaded> {
76 let elf = Elf::parse(bytes).context("not a valid elf")?;
77 if elf.header.e_machine != goblin::elf::header::EM_X86_64 {
78 bail!(
79 "only x86-64 is supported in this version (got e_machine {})",
80 elf.header.e_machine
81 );
82 }
83
84 let mut plans: Vec<(usize, usize, usize, u64, bool, bool)> = Vec::new();
93 let mut total_mem: u64 = 0;
94 for ph in &elf.program_headers {
95 if ph.p_type != goblin::elf::program_header::PT_LOAD {
96 continue;
97 }
98 if ph.p_memsz > MAX_SEG_MEM {
100 bail!(
101 "PT_LOAD p_memsz {} over {}-byte limit, refusing",
102 ph.p_memsz,
103 MAX_SEG_MEM
104 );
105 }
106 if ph.p_vaddr.checked_add(ph.p_memsz).is_none() {
109 bail!("PT_LOAD vaddr {:#x} + memsz overflows", ph.p_vaddr);
110 }
111
112 let start = usize::try_from(ph.p_offset)
116 .unwrap_or(usize::MAX)
117 .min(bytes.len());
118 let fsz = usize::try_from(ph.p_filesz).unwrap_or(usize::MAX);
119 let end = start.saturating_add(fsz).min(bytes.len());
120 let memsz = usize::try_from(ph.p_memsz).unwrap_or(usize::MAX);
122
123 let alloc_len = (end - start).max(memsz); total_mem = total_mem.saturating_add(alloc_len as u64);
125 if total_mem > MAX_TOTAL_MEM {
126 bail!("total PT_LOAD memory over {}-byte limit", MAX_TOTAL_MEM);
127 }
128 plans.push((
129 start,
130 end,
131 memsz,
132 ph.p_vaddr,
133 ph.is_executable(),
134 ph.is_write(),
135 ));
136 }
137 if plans.is_empty() {
138 bail!("no PT_LOAD segments");
139 }
140
141 let mut segments = Vec::with_capacity(plans.len());
143 for (start, end, memsz, vaddr, exec, write) in plans {
144 let mut data = Vec::with_capacity((end - start).max(memsz));
145 data.extend_from_slice(&bytes[start..end]);
146 if memsz > data.len() {
148 data.resize(memsz, 0);
149 }
150 segments.push(Segment {
151 vaddr,
152 bytes: data,
153 exec,
154 write,
155 });
156 }
157
158 let is_pie = elf.header.e_type == goblin::elf::header::ET_DYN;
159
160 let mut funcs = discover(&elf, bytes)?;
161 funcs.sort_by(|a, b| {
163 a.entry
164 .cmp(&b.entry)
165 .then(b.name.is_some().cmp(&a.name.is_some()))
166 });
167 funcs.dedup_by_key(|f| f.entry);
168
169 Ok(Loaded {
170 image: Image {
171 segments,
172 entry: elf.header.e_entry,
173 is_pie,
174 },
175 funcs,
176 })
177}
178
179fn discover(elf: &Elf, raw: &[u8]) -> Result<Vec<Func>> {
180 let mut out = Vec::new();
181
182 for (sym, src) in elf
183 .syms
184 .iter()
185 .map(|s| (s, FuncSource::Symtab))
186 .chain(elf.dynsyms.iter().map(|s| (s, FuncSource::DynSym)))
187 {
188 if sym.st_type() != goblin::elf::sym::STT_FUNC {
189 continue;
190 }
191 if sym.st_value == 0 || sym.st_size == 0 {
192 continue; }
194 let name = match src {
195 FuncSource::Symtab => elf.strtab.get_at(sym.st_name),
196 _ => elf.dynstrtab.get_at(sym.st_name),
197 }
198 .map(|s| s.to_string())
199 .filter(|s| !s.is_empty());
200 out.push(Func {
201 name,
202 entry: sym.st_value,
203 size: sym.st_size,
204 source: src,
205 });
206 }
207
208 if out.is_empty() {
210 if let Some(mut fdes) = eh_frame_funcs(elf, raw) {
211 out.append(&mut fdes);
212 }
213 }
214
215 Ok(out)
216}
217
218fn eh_frame_funcs(elf: &Elf, raw: &[u8]) -> Option<Vec<Func>> {
220 use gimli::{BaseAddresses, CieOrFde, EhFrame, LittleEndian, UnwindSection};
221
222 let sh = elf
223 .section_headers
224 .iter()
225 .find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(".eh_frame"))?;
226 let start = usize::try_from(sh.sh_offset).ok()?;
230 let size = usize::try_from(sh.sh_size).ok()?;
231 let end = start.checked_add(size)?;
232 let data = raw.get(start..end)?;
233
234 let eh = EhFrame::new(data, LittleEndian);
235 let bases = BaseAddresses::default().set_eh_frame(sh.sh_addr);
236
237 let mut entries = eh.entries(&bases);
238 let mut out = Vec::new();
239 loop {
240 match entries.next() {
241 Ok(Some(CieOrFde::Fde(partial))) => {
242 if let Ok(fde) = partial.parse(EhFrame::cie_from_offset) {
243 let entry = fde.initial_address();
244 let size = fde.len();
245 if size > 0 {
246 out.push(Func {
247 name: None,
248 entry,
249 size,
250 source: FuncSource::EhFrame,
251 });
252 }
253 }
254 }
255 Ok(Some(CieOrFde::Cie(_))) => {}
256 Ok(None) => break,
257 Err(_) => break,
258 }
259 }
260 if out.is_empty() {
261 None
262 } else {
263 Some(out)
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use super::*;
270
271 #[test]
272 fn garbage_input_errors_not_panics() {
273 assert!(load(b"").is_err());
274 assert!(load(b"not an elf at all, just text here").is_err());
275 let mut junk = vec![0x7f, b'E', b'L', b'F'];
277 junk.extend(std::iter::repeat_n(0x41u8, 400));
278 let _ = load(&junk); }
280
281 #[test]
282 fn truncated_header_does_not_panic() {
283 let mut hdr = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0];
284 hdr.extend(std::iter::repeat_n(0u8, 48));
285 let _ = load(&hdr);
286 }
287
288 fn craft_elf(p_offset: u64, p_vaddr: u64, p_filesz: u64, p_memsz: u64) -> Vec<u8> {
292 let mut e = vec![0u8; 64 + 56];
293 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
294 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
298 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
299 let put32 =
300 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
301 let put64 =
302 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
303 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;
312 put32(&mut e, ph, 1); put32(&mut e, ph + 4, 5); put64(&mut e, ph + 8, p_offset);
315 put64(&mut e, ph + 16, p_vaddr);
316 put64(&mut e, ph + 32, p_filesz);
317 put64(&mut e, ph + 40, p_memsz);
318 put64(&mut e, ph + 48, 0x1000); e
320 }
321
322 #[test]
323 fn huge_memsz_is_refused_not_allocated() {
324 let elf = craft_elf(0, 0x1000, 0, u64::MAX);
326 assert!(load(&elf).is_err());
327 let elf = craft_elf(0, 0x1000, 0, MAX_SEG_MEM + 1);
329 assert!(load(&elf).is_err());
330 }
331
332 #[test]
333 fn vaddr_plus_memsz_overflow_is_refused() {
334 let elf = craft_elf(0, u64::MAX - 16, 0, 4096);
335 assert!(load(&elf).is_err());
336 }
337
338 fn craft_elf_many_load(n: u16) -> Vec<u8> {
343 let phoff = 64usize;
344 let file_len = phoff + (n as usize) * 56;
345 let mut e = vec![0u8; file_len];
346 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
347 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
351 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
352 let put32 =
353 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
354 let put64 =
355 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
356 put16(&mut e, 16, 2); put16(&mut e, 18, 62); put32(&mut e, 20, 1);
359 put64(&mut e, 32, phoff as u64);
360 put16(&mut e, 52, 64);
361 put16(&mut e, 54, 56);
362 put16(&mut e, 56, n);
363 for i in 0..n as usize {
364 let ph = phoff + i * 56;
365 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);
372 }
373 e
374 }
375
376 #[test]
377 fn overlapping_file_backed_segments_are_refused_not_allocated() {
378 let elf = craft_elf_many_load(20000);
381 let r = load(&elf);
382 assert!(
383 r.is_err(),
384 "expected the alloc-amplification bomb to be refused"
385 );
386 }
387
388 #[test]
389 fn offset_past_eof_does_not_panic() {
390 let elf = craft_elf(0xffff_0000, 0x1000, 32, 32);
392 let _ = load(&elf); }
394
395 #[test]
396 fn code_at_high_vaddr_no_overflow() {
397 let img = Image {
400 segments: vec![Segment {
401 vaddr: u64::MAX - 8,
402 bytes: vec![0u8; 8],
403 exec: true,
404 write: false,
405 }],
406 entry: 0,
407 is_pie: false,
408 };
409 assert!(img.code_at(u64::MAX - 4, 64).is_none());
410 assert!(img.code_at(u64::MAX, 16).is_none());
411 }
412}