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 segments = Vec::new();
85 let mut total_mem: u64 = 0;
86 for ph in &elf.program_headers {
87 if ph.p_type != goblin::elf::program_header::PT_LOAD {
88 continue;
89 }
90 if ph.p_memsz > MAX_SEG_MEM {
92 bail!(
93 "PT_LOAD p_memsz {} over {}-byte limit, refusing",
94 ph.p_memsz,
95 MAX_SEG_MEM
96 );
97 }
98 if ph.p_vaddr.checked_add(ph.p_memsz).is_none() {
101 bail!("PT_LOAD vaddr {:#x} + memsz overflows", ph.p_vaddr);
102 }
103 total_mem = total_mem.saturating_add(ph.p_memsz);
104 if total_mem > MAX_TOTAL_MEM {
105 bail!("total PT_LOAD memory over {}-byte limit", MAX_TOTAL_MEM);
106 }
107
108 let start = usize::try_from(ph.p_offset)
112 .unwrap_or(usize::MAX)
113 .min(bytes.len());
114 let fsz = usize::try_from(ph.p_filesz).unwrap_or(usize::MAX);
115 let end = start.saturating_add(fsz).min(bytes.len());
116 let mut data = bytes[start..end].to_vec();
117 let memsz = usize::try_from(ph.p_memsz).unwrap_or(usize::MAX);
120 if memsz > data.len() {
121 data.resize(memsz, 0);
122 }
123 segments.push(Segment {
124 vaddr: ph.p_vaddr,
125 bytes: data,
126 exec: ph.is_executable(),
127 write: ph.is_write(),
128 });
129 }
130 if segments.is_empty() {
131 bail!("no PT_LOAD segments");
132 }
133
134 let is_pie = elf.header.e_type == goblin::elf::header::ET_DYN;
135
136 let mut funcs = discover(&elf, bytes)?;
137 funcs.sort_by(|a, b| {
139 a.entry
140 .cmp(&b.entry)
141 .then(b.name.is_some().cmp(&a.name.is_some()))
142 });
143 funcs.dedup_by_key(|f| f.entry);
144
145 Ok(Loaded {
146 image: Image {
147 segments,
148 entry: elf.header.e_entry,
149 is_pie,
150 },
151 funcs,
152 })
153}
154
155fn discover(elf: &Elf, raw: &[u8]) -> Result<Vec<Func>> {
156 let mut out = Vec::new();
157
158 for (sym, src) in elf
159 .syms
160 .iter()
161 .map(|s| (s, FuncSource::Symtab))
162 .chain(elf.dynsyms.iter().map(|s| (s, FuncSource::DynSym)))
163 {
164 if sym.st_type() != goblin::elf::sym::STT_FUNC {
165 continue;
166 }
167 if sym.st_value == 0 || sym.st_size == 0 {
168 continue; }
170 let name = match src {
171 FuncSource::Symtab => elf.strtab.get_at(sym.st_name),
172 _ => elf.dynstrtab.get_at(sym.st_name),
173 }
174 .map(|s| s.to_string())
175 .filter(|s| !s.is_empty());
176 out.push(Func {
177 name,
178 entry: sym.st_value,
179 size: sym.st_size,
180 source: src,
181 });
182 }
183
184 if out.is_empty() {
186 if let Some(mut fdes) = eh_frame_funcs(elf, raw) {
187 out.append(&mut fdes);
188 }
189 }
190
191 Ok(out)
192}
193
194fn eh_frame_funcs(elf: &Elf, raw: &[u8]) -> Option<Vec<Func>> {
196 use gimli::{BaseAddresses, CieOrFde, EhFrame, LittleEndian, UnwindSection};
197
198 let sh = elf
199 .section_headers
200 .iter()
201 .find(|s| elf.shdr_strtab.get_at(s.sh_name) == Some(".eh_frame"))?;
202 let start = usize::try_from(sh.sh_offset).ok()?;
206 let size = usize::try_from(sh.sh_size).ok()?;
207 let end = start.checked_add(size)?;
208 let data = raw.get(start..end)?;
209
210 let eh = EhFrame::new(data, LittleEndian);
211 let bases = BaseAddresses::default().set_eh_frame(sh.sh_addr);
212
213 let mut entries = eh.entries(&bases);
214 let mut out = Vec::new();
215 loop {
216 match entries.next() {
217 Ok(Some(CieOrFde::Fde(partial))) => {
218 if let Ok(fde) = partial.parse(EhFrame::cie_from_offset) {
219 let entry = fde.initial_address();
220 let size = fde.len();
221 if size > 0 {
222 out.push(Func {
223 name: None,
224 entry,
225 size,
226 source: FuncSource::EhFrame,
227 });
228 }
229 }
230 }
231 Ok(Some(CieOrFde::Cie(_))) => {}
232 Ok(None) => break,
233 Err(_) => break,
234 }
235 }
236 if out.is_empty() {
237 None
238 } else {
239 Some(out)
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn garbage_input_errors_not_panics() {
249 assert!(load(b"").is_err());
250 assert!(load(b"not an elf at all, just text here").is_err());
251 let mut junk = vec![0x7f, b'E', b'L', b'F'];
253 junk.extend(std::iter::repeat_n(0x41u8, 400));
254 let _ = load(&junk); }
256
257 #[test]
258 fn truncated_header_does_not_panic() {
259 let mut hdr = vec![0x7f, b'E', b'L', b'F', 2, 1, 1, 0];
260 hdr.extend(std::iter::repeat_n(0u8, 48));
261 let _ = load(&hdr);
262 }
263
264 fn craft_elf(p_offset: u64, p_vaddr: u64, p_filesz: u64, p_memsz: u64) -> Vec<u8> {
268 let mut e = vec![0u8; 64 + 56];
269 e[0..4].copy_from_slice(&[0x7f, b'E', b'L', b'F']);
270 e[4] = 2; e[5] = 1; e[6] = 1; let put16 =
274 |e: &mut [u8], off: usize, v: u16| e[off..off + 2].copy_from_slice(&v.to_le_bytes());
275 let put32 =
276 |e: &mut [u8], off: usize, v: u32| e[off..off + 4].copy_from_slice(&v.to_le_bytes());
277 let put64 =
278 |e: &mut [u8], off: usize, v: u64| e[off..off + 8].copy_from_slice(&v.to_le_bytes());
279 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;
288 put32(&mut e, ph, 1); put32(&mut e, ph + 4, 5); put64(&mut e, ph + 8, p_offset);
291 put64(&mut e, ph + 16, p_vaddr);
292 put64(&mut e, ph + 32, p_filesz);
293 put64(&mut e, ph + 40, p_memsz);
294 put64(&mut e, ph + 48, 0x1000); e
296 }
297
298 #[test]
299 fn huge_memsz_is_refused_not_allocated() {
300 let elf = craft_elf(0, 0x1000, 0, u64::MAX);
302 assert!(load(&elf).is_err());
303 let elf = craft_elf(0, 0x1000, 0, MAX_SEG_MEM + 1);
305 assert!(load(&elf).is_err());
306 }
307
308 #[test]
309 fn vaddr_plus_memsz_overflow_is_refused() {
310 let elf = craft_elf(0, u64::MAX - 16, 0, 4096);
311 assert!(load(&elf).is_err());
312 }
313
314 #[test]
315 fn offset_past_eof_does_not_panic() {
316 let elf = craft_elf(0xffff_0000, 0x1000, 32, 32);
318 let _ = load(&elf); }
320
321 #[test]
322 fn code_at_high_vaddr_no_overflow() {
323 let img = Image {
326 segments: vec![Segment {
327 vaddr: u64::MAX - 8,
328 bytes: vec![0u8; 8],
329 exec: true,
330 write: false,
331 }],
332 entry: 0,
333 is_pie: false,
334 };
335 assert!(img.code_at(u64::MAX - 4, 64).is_none());
336 assert!(img.code_at(u64::MAX, 16).is_none());
337 }
338}