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
183fn discover(elf: &Elf, raw: &[u8]) -> Result<Vec<Func>> {
184 let mut out = Vec::new();
185
186 for (sym, src) in elf
187 .syms
188 .iter()
189 .map(|s| (s, FuncSource::Symtab))
190 .chain(elf.dynsyms.iter().map(|s| (s, FuncSource::DynSym)))
191 {
192 if sym.st_type() != goblin::elf::sym::STT_FUNC {
193 continue;
194 }
195 if sym.st_value == 0 || sym.st_size == 0 {
196 continue; }
198 let name = match src {
199 FuncSource::Symtab => elf.strtab.get_at(sym.st_name),
200 _ => elf.dynstrtab.get_at(sym.st_name),
201 }
202 .map(|s| s.to_string())
203 .filter(|s| !s.is_empty());
204 out.push(Func {
205 name,
206 entry: sym.st_value,
207 size: sym.st_size,
208 source: src,
209 });
210 }
211
212 if out.is_empty() {
214 if let Some(mut fdes) = eh_frame_funcs(elf, raw) {
215 out.append(&mut fdes);
216 }
217 }
218
219 Ok(out)
220}
221
222fn eh_frame_funcs(elf: &Elf, raw: &[u8]) -> Option<Vec<Func>> {
224 use gimli::{BaseAddresses, CieOrFde, EhFrame, LittleEndian, UnwindSection};
225
226 let sh = elf
227 .section_headers
228 .iter()
229 .find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(".eh_frame"))?;
230 let start = usize::try_from(sh.sh_offset).ok()?;
234 let size = usize::try_from(sh.sh_size).ok()?;
235 let end = start.checked_add(size)?;
236 let data = raw.get(start..end)?;
237
238 let eh = EhFrame::new(data, LittleEndian);
239 let bases = BaseAddresses::default().set_eh_frame(sh.sh_addr);
240
241 let mut entries = eh.entries(&bases);
242 let mut out = Vec::new();
243 loop {
244 match entries.next() {
245 Ok(Some(CieOrFde::Fde(partial))) => {
246 if let Ok(fde) = partial.parse(EhFrame::cie_from_offset) {
247 let entry = fde.initial_address();
248 let size = fde.len();
249 if size > 0 {
250 out.push(Func {
251 name: None,
252 entry,
253 size,
254 source: FuncSource::EhFrame,
255 });
256 }
257 }
258 }
259 Ok(Some(CieOrFde::Cie(_))) => {}
260 Ok(None) => break,
261 Err(_) => break,
262 }
263 }
264 if out.is_empty() {
265 None
266 } else {
267 Some(out)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn garbage_input_errors_not_panics() {
277 assert!(load(b"").is_err());
278 assert!(load(b"not an elf at all, just text here").is_err());
279 let mut junk = vec![0x7f, b'E', b'L', b'F'];
281 junk.extend(std::iter::repeat_n(0x41u8, 400));
282 let _ = load(&junk); }
284
285 #[test]
286 fn truncated_header_does_not_panic() {
287 let mut hdr = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0];
288 hdr.extend(std::iter::repeat_n(0u8, 48));
289 let _ = load(&hdr);
290 }
291
292 fn craft_elf(p_offset: u64, p_vaddr: u64, p_filesz: u64, p_memsz: u64) -> Vec<u8> {
296 let mut e = vec![0u8; 64 + 56];
297 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
298 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
302 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
303 let put32 =
304 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
305 let put64 =
306 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
307 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;
316 put32(&mut e, ph, 1); put32(&mut e, ph + 4, 5); put64(&mut e, ph + 8, p_offset);
319 put64(&mut e, ph + 16, p_vaddr);
320 put64(&mut e, ph + 32, p_filesz);
321 put64(&mut e, ph + 40, p_memsz);
322 put64(&mut e, ph + 48, 0x1000); e
324 }
325
326 #[test]
327 fn huge_memsz_is_refused_not_allocated() {
328 let elf = craft_elf(0, 0x1000, 0, u64::MAX);
330 assert!(load(&elf).is_err());
331 let elf = craft_elf(0, 0x1000, 0, MAX_SEG_MEM + 1);
333 assert!(load(&elf).is_err());
334 }
335
336 #[test]
337 fn vaddr_plus_memsz_overflow_is_refused() {
338 let elf = craft_elf(0, u64::MAX - 16, 0, 4096);
339 assert!(load(&elf).is_err());
340 }
341
342 fn craft_elf_many_load(n: u16) -> Vec<u8> {
347 let phoff = 64usize;
348 let file_len = phoff + (n as usize) * 56;
349 let mut e = vec![0u8; file_len];
350 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
351 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
355 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
356 let put32 =
357 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
358 let put64 =
359 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
360 put16(&mut e, 16, 2); put16(&mut e, 18, 62); put32(&mut e, 20, 1);
363 put64(&mut e, 32, phoff as u64);
364 put16(&mut e, 52, 64);
365 put16(&mut e, 54, 56);
366 put16(&mut e, 56, n);
367 for i in 0..n as usize {
368 let ph = phoff + i * 56;
369 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);
376 }
377 e
378 }
379
380 #[test]
381 fn overlapping_file_backed_segments_are_refused_not_allocated() {
382 let elf = craft_elf_many_load(20000);
385 let r = load(&elf);
386 assert!(
387 r.is_err(),
388 "expected the alloc-amplification bomb to be refused"
389 );
390 }
391
392 #[test]
393 fn offset_past_eof_does_not_panic() {
394 let elf = craft_elf(0xffff_0000, 0x1000, 32, 32);
396 let _ = load(&elf); }
398
399 #[test]
400 fn code_at_high_vaddr_no_overflow() {
401 let img = Image {
404 segments: vec![Segment {
405 vaddr: u64::MAX - 8,
406 bytes: vec![0u8; 8],
407 exec: true,
408 write: false,
409 }],
410 entry: 0,
411 is_pie: false,
412 };
413 assert!(img.code_at(u64::MAX - 4, 64).is_none());
414 assert!(img.code_at(u64::MAX, 16).is_none());
415 }
416}