solana_rbpf/elf_parser/
mod.rs

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