1pub mod consts;
4pub mod types;
5
6use std::{fmt, mem, ops::Range, slice};
7
8use crate::{ArithmeticOverflow, ErrCheckedArithmetic};
9use {consts::*, types::*};
10
11pub const SECTION_NAME_LENGTH_MAXIMUM: usize = 16;
13const SYMBOL_NAME_LENGTH_MAXIMUM: usize = 64;
14
15#[derive(Debug, PartialEq, Eq, thiserror::Error)]
17pub enum ElfParserError {
18 #[error("invalid file header")]
20 InvalidFileHeader,
21 #[error("invalid program header")]
23 InvalidProgramHeader,
24 #[error("invalid section header")]
26 InvalidSectionHeader,
27 #[error("invalid string")]
29 InvalidString,
30 #[error("Section or symbol name `{0}` is longer than `{1}` bytes")]
32 StringTooLong(String, usize),
33 #[error("value out of bounds")]
35 OutOfBounds,
36 #[error("invalid size")]
38 InvalidSize,
39 #[error("values overlap")]
41 Overlap,
42 #[error("sections not in ascending order")]
44 SectionNotInOrder,
45 #[error("no section name string table found")]
47 NoSectionNameStringTable,
48 #[error("invalid dynamic section table")]
50 InvalidDynamicSectionTable,
51 #[error("invalid relocation table")]
53 InvalidRelocationTable,
54 #[error("invalid alignment")]
56 InvalidAlignment,
57 #[error("no string table")]
59 NoStringTable,
60 #[error("no dynamic string table")]
62 NoDynamicStringTable,
63}
64
65impl Elf64Phdr {
66 pub fn file_range(&self) -> Option<Range<usize>> {
68 (self.p_type == PT_LOAD).then(|| {
69 let offset = self.p_offset as usize;
70 offset..offset.saturating_add(self.p_filesz as usize)
71 })
72 }
73
74 pub fn vm_range(&self) -> Range<Elf64Addr> {
76 let addr = self.p_vaddr;
77 addr..addr.saturating_add(self.p_memsz)
78 }
79}
80
81impl Elf64Shdr {
82 pub fn is_writable(&self) -> bool {
84 self.sh_flags & (SHF_ALLOC | SHF_WRITE) == SHF_ALLOC | SHF_WRITE
85 }
86
87 pub fn file_range(&self) -> Option<Range<usize>> {
89 (self.sh_type != SHT_NOBITS).then(|| {
90 let offset = self.sh_offset as usize;
91 offset..offset.saturating_add(self.sh_size as usize)
92 })
93 }
94
95 pub fn vm_range(&self) -> Range<Elf64Addr> {
97 self.sh_addr..self.sh_addr.saturating_add(self.sh_size)
98 }
99}
100
101impl Elf64Sym {
102 pub fn is_function(&self) -> bool {
104 (self.st_info & 0xF) == STT_FUNC
105 }
106}
107
108impl Elf64Rel {
109 pub fn r_type(&self) -> Elf64Word {
111 (self.r_info & 0xFFFFFFFF) as Elf64Word
112 }
113
114 pub fn r_sym(&self) -> Elf64Word {
116 self.r_info.checked_shr(32).unwrap_or(0) as Elf64Word
117 }
118}
119
120fn check_that_there_is_no_overlap(
121 range_a: &Range<usize>,
122 range_b: &Range<usize>,
123) -> Result<(), ElfParserError> {
124 if range_a.end <= range_b.start || range_b.end <= range_a.start {
125 Ok(())
126 } else {
127 Err(ElfParserError::Overlap)
128 }
129}
130
131pub struct Elf64<'a> {
133 elf_bytes: &'a [u8],
134 file_header: &'a Elf64Ehdr,
135 program_header_table: &'a [Elf64Phdr],
136 section_header_table: &'a [Elf64Shdr],
137 section_names_section_header: Option<&'a Elf64Shdr>,
138 symbol_section_header: Option<&'a Elf64Shdr>,
139 symbol_names_section_header: Option<&'a Elf64Shdr>,
140 dynamic_table: [Elf64Xword; DT_NUM],
141 dynamic_relocations_table: Option<&'a [Elf64Rel]>,
142 dynamic_symbol_table: Option<&'a [Elf64Sym]>,
143 dynamic_symbol_names_section_header: Option<&'a Elf64Shdr>,
144}
145
146impl<'a> Elf64<'a> {
147 pub fn parse(elf_bytes: &'a [u8]) -> Result<Self, ElfParserError> {
149 let (file_header_range, file_header) = Self::parse_file_header(elf_bytes)?;
150
151 if file_header.e_ident.ei_mag != ELFMAG
152 || file_header.e_ident.ei_class != ELFCLASS64
153 || file_header.e_ident.ei_data != ELFDATA2LSB
154 || file_header.e_ident.ei_version != EV_CURRENT as u8
155 || file_header.e_version != EV_CURRENT
156 || file_header.e_ehsize != mem::size_of::<Elf64Ehdr>() as u16
157 || file_header.e_phentsize != mem::size_of::<Elf64Phdr>() as u16
158 || file_header.e_shentsize != mem::size_of::<Elf64Shdr>() as u16
159 || file_header.e_shstrndx >= file_header.e_shnum
160 {
161 return Err(ElfParserError::InvalidFileHeader);
162 }
163
164 let (program_header_table_range, program_header_table) =
165 Self::parse_program_header_table(elf_bytes, file_header_range.clone(), file_header)?;
166
167 let (section_header_table_range, section_header_table) = Self::parse_section_header_table(
168 elf_bytes,
169 file_header_range.clone(),
170 file_header,
171 program_header_table_range.clone(),
172 )?;
173
174 section_header_table
175 .first()
176 .filter(|section_header| section_header.sh_type == SHT_NULL)
177 .ok_or(ElfParserError::InvalidSectionHeader)?;
178
179 let mut vaddr = 0;
180 for program_header in program_header_table {
181 if program_header.p_type != PT_LOAD {
182 continue;
183 }
184 if program_header.p_vaddr < vaddr {
186 return Err(ElfParserError::InvalidProgramHeader);
187 }
188 if program_header
189 .p_offset
190 .err_checked_add(program_header.p_filesz)? as usize
191 > elf_bytes.len()
192 {
193 return Err(ElfParserError::OutOfBounds);
194 }
195 vaddr = program_header.p_vaddr;
196 }
197
198 let mut offset = 0usize;
199 for section_header in section_header_table.iter() {
200 if section_header.sh_type == SHT_NOBITS {
201 continue;
202 }
203 let section_range = section_header.sh_offset as usize
204 ..(section_header.sh_offset as usize)
205 .err_checked_add(section_header.sh_size as usize)?;
206 check_that_there_is_no_overlap(§ion_range, &file_header_range)?;
207 check_that_there_is_no_overlap(§ion_range, &program_header_table_range)?;
208 check_that_there_is_no_overlap(§ion_range, §ion_header_table_range)?;
209 if section_range.start < offset {
210 return Err(ElfParserError::SectionNotInOrder);
211 }
212 offset = section_range.end;
213 if offset > elf_bytes.len() {
214 return Err(ElfParserError::OutOfBounds);
215 }
216 }
217
218 let section_names_section_header = (file_header.e_shstrndx != SHN_UNDEF)
219 .then(|| {
220 section_header_table
221 .get(file_header.e_shstrndx as usize)
222 .ok_or(ElfParserError::OutOfBounds)
223 })
224 .transpose()?;
225
226 let mut parser = Self {
227 elf_bytes,
228 file_header,
229 program_header_table,
230 section_header_table,
231 section_names_section_header,
232 symbol_section_header: None,
233 symbol_names_section_header: None,
234 dynamic_table: [0; DT_NUM],
235 dynamic_relocations_table: None,
236 dynamic_symbol_table: None,
237 dynamic_symbol_names_section_header: None,
238 };
239
240 parser.parse_sections()?;
241 parser.parse_dynamic()?;
242
243 Ok(parser)
244 }
245
246 pub fn file_header(&self) -> &Elf64Ehdr {
248 self.file_header
249 }
250
251 pub fn program_header_table(&self) -> &[Elf64Phdr] {
253 self.program_header_table
254 }
255
256 pub fn section_header_table(&self) -> &[Elf64Shdr] {
258 self.section_header_table
259 }
260
261 pub fn dynamic_symbol_table(&self) -> Option<&[Elf64Sym]> {
263 self.dynamic_symbol_table
264 }
265
266 pub fn dynamic_relocations_table(&self) -> Option<&[Elf64Rel]> {
268 self.dynamic_relocations_table
269 }
270
271 pub fn parse_file_header(
273 elf_bytes: &'a [u8],
274 ) -> Result<(std::ops::Range<usize>, &'a Elf64Ehdr), ElfParserError> {
275 let file_header_range = 0..mem::size_of::<Elf64Ehdr>();
276 let file_header_bytes = elf_bytes
277 .get(file_header_range.clone())
278 .ok_or(ElfParserError::OutOfBounds)?;
279 let ptr = file_header_bytes.as_ptr();
280 if (ptr as usize)
281 .checked_rem(mem::align_of::<Elf64Ehdr>())
282 .map(|remaining| remaining != 0)
283 .unwrap_or(true)
284 {
285 return Err(ElfParserError::InvalidAlignment);
286 }
287 let file_header = unsafe { &*ptr.cast::<Elf64Ehdr>() };
288 Ok((file_header_range, file_header))
289 }
290
291 pub fn parse_program_header_table(
293 elf_bytes: &'a [u8],
294 file_header_range: std::ops::Range<usize>,
295 file_header: &Elf64Ehdr,
296 ) -> Result<(std::ops::Range<usize>, &'a [Elf64Phdr]), ElfParserError> {
297 let program_header_table_range = file_header.e_phoff as usize
298 ..mem::size_of::<Elf64Phdr>()
299 .err_checked_mul(file_header.e_phnum as usize)?
300 .err_checked_add(file_header.e_phoff as usize)?;
301 check_that_there_is_no_overlap(&file_header_range, &program_header_table_range)?;
302 let program_header_table =
303 Self::slice_from_bytes::<Elf64Phdr>(elf_bytes, program_header_table_range.clone())?;
304 Ok((program_header_table_range, program_header_table))
305 }
306
307 pub fn parse_section_header_table(
309 elf_bytes: &'a [u8],
310 file_header_range: std::ops::Range<usize>,
311 file_header: &Elf64Ehdr,
312 program_header_table_range: std::ops::Range<usize>,
313 ) -> Result<(std::ops::Range<usize>, &'a [Elf64Shdr]), ElfParserError> {
314 let section_header_table_range = file_header.e_shoff as usize
315 ..mem::size_of::<Elf64Shdr>()
316 .err_checked_mul(file_header.e_shnum as usize)?
317 .err_checked_add(file_header.e_shoff as usize)?;
318 check_that_there_is_no_overlap(&file_header_range, §ion_header_table_range)?;
319 check_that_there_is_no_overlap(&program_header_table_range, §ion_header_table_range)?;
320 let section_header_table =
321 Self::slice_from_bytes::<Elf64Shdr>(elf_bytes, section_header_table_range.clone())?;
322 Ok((section_header_table_range, section_header_table))
323 }
324
325 fn parse_sections(&mut self) -> Result<(), ElfParserError> {
326 macro_rules! section_header_by_name {
327 ($self:expr, $section_header:expr, $section_name:expr,
328 $($name:literal => $field:ident,)*) => {
329 match $section_name {
330 $($name => {
331 if $self.$field.is_some() {
332 return Err(ElfParserError::InvalidSectionHeader);
333 }
334 $self.$field = Some($section_header);
335 })*
336 _ => {}
337 }
338 }
339 }
340 let section_names_section_header = self
341 .section_names_section_header
342 .ok_or(ElfParserError::NoSectionNameStringTable)?;
343 for section_header in self.section_header_table.iter() {
344 let section_name = Self::get_string_in_section(
345 self.elf_bytes,
346 section_names_section_header,
347 section_header.sh_name,
348 SECTION_NAME_LENGTH_MAXIMUM,
349 )?;
350 section_header_by_name!(
351 self, section_header, section_name,
352 b".symtab" => symbol_section_header,
353 b".strtab" => symbol_names_section_header,
354 b".dynstr" => dynamic_symbol_names_section_header,
355 )
356 }
357
358 Ok(())
359 }
360
361 fn parse_dynamic(&mut self) -> Result<(), ElfParserError> {
362 let mut dynamic_table: Option<&[Elf64Dyn]> = None;
363
364 if let Some(dynamic_program_header) = self
366 .program_header_table
367 .iter()
368 .find(|program_header| program_header.p_type == PT_DYNAMIC)
369 {
370 dynamic_table =
371 Self::slice_from_program_header(self.elf_bytes, dynamic_program_header).ok();
372 }
373
374 if dynamic_table.is_none() {
377 if let Some(dynamic_section_header) = self
378 .section_header_table
379 .iter()
380 .find(|section_header| section_header.sh_type == SHT_DYNAMIC)
381 {
382 dynamic_table = Some(
383 Self::slice_from_section_header(self.elf_bytes, dynamic_section_header)
384 .map_err(|_| ElfParserError::InvalidDynamicSectionTable)?,
385 );
386 }
387 }
388
389 let dynamic_table = match dynamic_table {
392 Some(table) => table,
393 None => return Ok(()),
394 };
395
396 for dyn_info in dynamic_table {
398 if dyn_info.d_tag == DT_NULL {
399 break;
400 }
401
402 if dyn_info.d_tag as usize >= DT_NUM {
403 continue;
405 }
406 self.dynamic_table[dyn_info.d_tag as usize] = dyn_info.d_val;
407 }
408
409 self.dynamic_relocations_table = self.parse_dynamic_relocations()?;
410 self.dynamic_symbol_table = self.parse_dynamic_symbol_table()?;
411
412 Ok(())
413 }
414
415 fn parse_dynamic_relocations(&mut self) -> Result<Option<&'a [Elf64Rel]>, ElfParserError> {
416 let vaddr = self.dynamic_table[DT_REL as usize];
417 if vaddr == 0 {
418 return Ok(None);
419 }
420
421 if self.dynamic_table[DT_RELENT as usize] as usize != mem::size_of::<Elf64Rel>() {
422 return Err(ElfParserError::InvalidDynamicSectionTable);
423 }
424
425 let size = self.dynamic_table[DT_RELSZ as usize] as usize;
426 if size == 0 {
427 return Err(ElfParserError::InvalidDynamicSectionTable);
428 }
429
430 let offset = if let Some(program_header) = self.program_header_for_vaddr(vaddr)? {
431 vaddr
432 .err_checked_sub(program_header.p_vaddr)?
433 .err_checked_add(program_header.p_offset)?
434 } else {
435 self.section_header_table
440 .iter()
441 .find(|section_header| section_header.sh_addr == vaddr)
442 .ok_or(ElfParserError::InvalidDynamicSectionTable)?
443 .sh_offset
444 } as usize;
445
446 Self::slice_from_bytes(self.elf_bytes, offset..offset.err_checked_add(size)?)
447 .map(Some)
448 .map_err(|_| ElfParserError::InvalidDynamicSectionTable)
449 }
450
451 fn parse_dynamic_symbol_table(&mut self) -> Result<Option<&'a [Elf64Sym]>, ElfParserError> {
452 let vaddr = self.dynamic_table[DT_SYMTAB as usize];
453 if vaddr == 0 {
454 return Ok(None);
455 }
456
457 let dynsym_section_header = self
458 .section_header_table
459 .iter()
460 .find(|section_header| section_header.sh_addr == vaddr)
461 .ok_or(ElfParserError::InvalidDynamicSectionTable)?;
462
463 self.get_symbol_table_of_section(dynsym_section_header)
464 .map(Some)
465 }
466
467 pub fn get_string_in_section(
469 elf_bytes: &'a [u8],
470 section_header: &Elf64Shdr,
471 offset_in_section: Elf64Word,
472 maximum_length: usize,
473 ) -> Result<&'a [u8], ElfParserError> {
474 if section_header.sh_type != SHT_STRTAB {
475 return Err(ElfParserError::InvalidSectionHeader);
476 }
477 let offset_in_file =
478 (section_header.sh_offset as usize).err_checked_add(offset_in_section as usize)?;
479 let string_range = offset_in_file
480 ..(section_header.sh_offset as usize)
481 .err_checked_add(section_header.sh_size as usize)?
482 .min(offset_in_file.err_checked_add(maximum_length)?);
483 let unterminated_string_bytes = elf_bytes
484 .get(string_range)
485 .ok_or(ElfParserError::OutOfBounds)?;
486 unterminated_string_bytes
487 .iter()
488 .position(|byte| *byte == 0x00)
489 .and_then(|string_length| unterminated_string_bytes.get(0..string_length))
490 .ok_or_else(|| {
491 ElfParserError::StringTooLong(
492 String::from_utf8_lossy(unterminated_string_bytes).to_string(),
493 maximum_length,
494 )
495 })
496 }
497
498 pub fn section_name(&self, sh_name: Elf64Word) -> Result<&'a [u8], ElfParserError> {
500 Self::get_string_in_section(
501 self.elf_bytes,
502 self.section_names_section_header
503 .ok_or(ElfParserError::NoSectionNameStringTable)?,
504 sh_name,
505 SECTION_NAME_LENGTH_MAXIMUM,
506 )
507 }
508
509 pub fn symbol_name(&self, st_name: Elf64Word) -> Result<&'a [u8], ElfParserError> {
511 Self::get_string_in_section(
512 self.elf_bytes,
513 self.symbol_names_section_header
514 .ok_or(ElfParserError::NoStringTable)?,
515 st_name,
516 SYMBOL_NAME_LENGTH_MAXIMUM,
517 )
518 }
519
520 pub fn symbol_table(&self) -> Result<Option<&'a [Elf64Sym]>, ElfParserError> {
522 self.symbol_section_header
523 .map(|section_header| self.get_symbol_table_of_section(section_header))
524 .transpose()
525 }
526
527 pub fn dynamic_symbol_name(&self, st_name: Elf64Word) -> Result<&'a [u8], ElfParserError> {
529 Self::get_string_in_section(
530 self.elf_bytes,
531 self.dynamic_symbol_names_section_header
532 .ok_or(ElfParserError::NoDynamicStringTable)?,
533 st_name,
534 SYMBOL_NAME_LENGTH_MAXIMUM,
535 )
536 }
537
538 pub fn get_symbol_table_of_section(
540 &self,
541 section_header: &Elf64Shdr,
542 ) -> Result<&'a [Elf64Sym], ElfParserError> {
543 if section_header.sh_type != SHT_SYMTAB && section_header.sh_type != SHT_DYNSYM {
544 return Err(ElfParserError::InvalidSectionHeader);
545 }
546
547 Self::slice_from_section_header(self.elf_bytes, section_header)
548 }
549
550 pub fn slice_from_program_header<T: 'static>(
553 bytes: &'a [u8],
554 &Elf64Phdr {
555 p_offset, p_filesz, ..
556 }: &Elf64Phdr,
557 ) -> Result<&'a [T], ElfParserError> {
558 Self::slice_from_bytes(
559 bytes,
560 (p_offset as usize)..(p_offset as usize).err_checked_add(p_filesz as usize)?,
561 )
562 }
563
564 pub fn slice_from_section_header<T: 'static>(
567 bytes: &'a [u8],
568 &Elf64Shdr {
569 sh_offset, sh_size, ..
570 }: &Elf64Shdr,
571 ) -> Result<&'a [T], ElfParserError> {
572 Self::slice_from_bytes(
573 bytes,
574 (sh_offset as usize)..(sh_offset as usize).err_checked_add(sh_size as usize)?,
575 )
576 }
577
578 pub fn slice_from_bytes<T: 'static>(
580 bytes: &[u8],
581 range: Range<usize>,
582 ) -> Result<&[T], ElfParserError> {
583 if range
584 .len()
585 .checked_rem(mem::size_of::<T>())
586 .map(|remainder| remainder != 0)
587 .unwrap_or(true)
588 {
589 return Err(ElfParserError::InvalidSize);
590 }
591
592 let bytes = bytes
593 .get(range.clone())
594 .ok_or(ElfParserError::OutOfBounds)?;
595
596 let ptr = bytes.as_ptr();
597 if (ptr as usize)
598 .checked_rem(mem::align_of::<T>())
599 .map(|remaining| remaining != 0)
600 .unwrap_or(true)
601 {
602 return Err(ElfParserError::InvalidAlignment);
603 }
604
605 Ok(unsafe {
606 slice::from_raw_parts(
607 ptr.cast(),
608 range.len().checked_div(mem::size_of::<T>()).unwrap_or(0),
609 )
610 })
611 }
612
613 fn program_header_for_vaddr(
614 &self,
615 vaddr: Elf64Addr,
616 ) -> Result<Option<&'a Elf64Phdr>, ElfParserError> {
617 for program_header in self.program_header_table.iter() {
618 let Elf64Phdr {
619 p_vaddr, p_memsz, ..
620 } = program_header;
621
622 if (*p_vaddr..p_vaddr.err_checked_add(*p_memsz)?).contains(&vaddr) {
623 return Ok(Some(program_header));
624 }
625 }
626 Ok(None)
627 }
628}
629
630impl fmt::Debug for Elf64<'_> {
631 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
632 writeln!(f, "{:#X?}", self.file_header)?;
633 for program_header in self.program_header_table.iter() {
634 writeln!(f, "{program_header:#X?}")?;
635 }
636 for section_header in self.section_header_table.iter() {
637 let section_name = Self::get_string_in_section(
638 self.elf_bytes,
639 self.section_names_section_header.unwrap(),
640 section_header.sh_name,
641 SECTION_NAME_LENGTH_MAXIMUM,
642 )
643 .and_then(|name| std::str::from_utf8(name).map_err(|_| ElfParserError::InvalidString))
644 .unwrap();
645 writeln!(f, "{section_name}")?;
646 writeln!(f, "{section_header:#X?}")?;
647 }
648 if let Some(section_header) = self.symbol_section_header {
649 let symbol_table = self.get_symbol_table_of_section(section_header).unwrap();
650 writeln!(f, "{symbol_table:#X?}")?;
651 for symbol in symbol_table.iter() {
652 if symbol.st_name != 0 {
653 let symbol_name = Self::get_string_in_section(
654 self.elf_bytes,
655 self.symbol_names_section_header.unwrap(),
656 symbol.st_name,
657 SYMBOL_NAME_LENGTH_MAXIMUM,
658 )
659 .and_then(|name| {
660 std::str::from_utf8(name).map_err(|_| ElfParserError::InvalidString)
661 })
662 .unwrap();
663 writeln!(f, "{symbol_name}")?;
664 }
665 }
666 }
667 Ok(())
668 }
669}
670
671impl From<ArithmeticOverflow> for ElfParserError {
672 fn from(_: ArithmeticOverflow) -> ElfParserError {
673 ElfParserError::OutOfBounds
674 }
675}