moto_runtime/external/elfloader/
binary.rs

1use super::{
2    DynamicFlags1, DynamicInfo, ElfLoader, ElfLoaderErr, LoadableHeaders, RelocationEntry,
3    RelocationType,
4};
5use core::fmt;
6
7use crate::external::xmas_elf;
8
9use xmas_elf::dynamic::Tag;
10use xmas_elf::program::ProgramHeader::{self, Ph32, Ph64};
11use xmas_elf::program::{ProgramIter, SegmentData, Type};
12use xmas_elf::sections::SectionData;
13pub use xmas_elf::symbol_table::Entry;
14use xmas_elf::ElfFile;
15use xmas_elf::*;
16
17/// Abstract representation of a loadable ELF binary.
18pub struct ElfBinary<'s> {
19    /// The ELF file in question.
20    pub file: ElfFile<'s>,
21    /// Parsed information from the .dynamic section (if the binary has it).
22    pub dynamic: Option<DynamicInfo>,
23}
24
25impl<'s> fmt::Debug for ElfBinary<'s> {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        write!(f, "ElfBinary{{ [")?;
28        for p in self.program_headers() {
29            write!(f, " pheader = {}", p)?;
30        }
31        write!(f, "] }}")
32    }
33}
34
35impl<'s> ElfBinary<'s> {
36    /// Create a new ElfBinary.
37    pub fn new(region: &'s [u8]) -> Result<ElfBinary<'s>, ElfLoaderErr> {
38        let file = ElfFile::new(region)?;
39
40        // Parse relevant parts out of the theĀ .dynamic section
41        let mut dynamic = None;
42        for p in file.program_iter() {
43            let typ = match p {
44                Ph64(header) => header.get_type()?,
45                Ph32(header) => header.get_type()?,
46            };
47
48            if typ == Type::Dynamic {
49                dynamic = ElfBinary::parse_dynamic(&file, &p)?;
50                break;
51            }
52        }
53
54        Ok(ElfBinary { file, dynamic })
55    }
56
57    /// Returns true if the binary is compiled as position independent code or false otherwise.
58    ///
59    /// For the binary to be PIE it needs to have a .dynamic section with PIE set in the flags1
60    /// field.
61    pub fn is_pie(&self) -> bool {
62        self.dynamic.as_ref().map_or(false, |d: &DynamicInfo| {
63            d.flags1.contains(DynamicFlags1::PIE)
64        })
65    }
66
67    /// Returns the dynamic loader if present.
68    ///
69    /// readelf -x .interp <binary>
70    ///
71    /// For a statically compiled binary this will return None
72    pub fn interpreter(&'s self) -> Option<&'s str> {
73        let section = self.file.find_section_by_name(".interp");
74        section.and_then(|interp_section| {
75            let data = interp_section.get_data(&self.file).ok()?;
76            let cstr = match data {
77                SectionData::Undefined(val) => val,
78                _ => return None,
79            };
80
81            // Validate there is room for a null terminator
82            if cstr.len() < 2 {
83                return None;
84            }
85
86            // Ensure it is a valid utf8 string
87            core::str::from_utf8(&cstr[..cstr.len() - 1]).ok()
88        })
89    }
90
91    /// Returns the target architecture
92    pub fn get_arch(&self) -> header::Machine {
93        self.file.header.pt2.machine().as_machine()
94    }
95
96    /// Return the entry point of the ELF file.
97    ///
98    /// Note this may be zero in case of position independent executables.
99    pub fn entry_point(&self) -> u64 {
100        self.file.header.pt2.entry_point()
101    }
102
103    /// Create a slice of the program headers.
104    pub fn program_headers(&self) -> ProgramIter {
105        self.file.program_iter()
106    }
107
108    /// Get the name of the sectione
109    pub fn symbol_name(&self, symbol: &'s dyn Entry) -> &'s str {
110        symbol.get_name(&self.file).unwrap_or("unknown")
111    }
112
113    /// Enumerate all the symbols in the file
114    pub fn for_each_symbol<F: FnMut(&'s dyn Entry)>(
115        &self,
116        mut func: F,
117    ) -> Result<(), ElfLoaderErr> {
118        let symbol_section = self
119            .file
120            .find_section_by_name(".symtab")
121            .ok_or(ElfLoaderErr::SymbolTableNotFound)?;
122        let symbol_table = symbol_section.get_data(&self.file)?;
123        match symbol_table {
124            SectionData::SymbolTable32(entries) => {
125                for entry in entries {
126                    func(entry);
127                }
128                Ok(())
129            }
130            SectionData::SymbolTable64(entries) => {
131                for entry in entries {
132                    func(entry);
133                }
134                Ok(())
135            }
136            _ => Err(ElfLoaderErr::SymbolTableNotFound),
137        }
138    }
139
140    /// Can we load this binary on our platform?
141    fn is_loadable(&self) -> Result<(), ElfLoaderErr> {
142        let header = self.file.header;
143        let typ = header.pt2.type_().as_type();
144
145        if header.pt1.version() != header::Version::Current {
146            Err(ElfLoaderErr::UnsupportedElfVersion)
147        } else if header.pt1.data() != header::Data::LittleEndian {
148            Err(ElfLoaderErr::UnsupportedEndianness)
149        } else if !(header.pt1.os_abi() == header::OsAbi::SystemV
150            || header.pt1.os_abi() == header::OsAbi::Linux)
151        {
152            Err(ElfLoaderErr::UnsupportedAbi)
153        } else if !(typ == header::Type::Executable || typ == header::Type::SharedObject) {
154            Err(ElfLoaderErr::UnsupportedElfType)
155        } else {
156            Ok(())
157        }
158    }
159
160    /// Process the relocation entries for the ELF file.
161    ///
162    /// Issues call to `loader.relocate` and passes the relocation entry.
163    fn maybe_relocate(&self, loader: &mut dyn ElfLoader) -> Result<(), ElfLoaderErr> {
164        // Relocation types are architecture specific
165        let arch = self.get_arch();
166
167        // It's easier to just locate the section by name, either:
168        // - .rela.dyn
169        // - .rel.dyn
170        let relocation_section = self
171            .file
172            .find_section_by_name(".rela.dyn")
173            .or_else(|| self.file.find_section_by_name(".rel.dyn"));
174
175        // Helper macro to call loader.relocate() on all entries
176        macro_rules! iter_entries_and_relocate {
177            ($rela_entries:expr, $create_addend:ident) => {
178                for entry in $rela_entries {
179                    loader.relocate(RelocationEntry {
180                        rtype: RelocationType::from(arch, entry.get_type() as u32)?,
181                        offset: entry.get_offset() as u64,
182                        index: entry.get_symbol_table_index(),
183                        addend: $create_addend!(entry),
184                    })?;
185                }
186            };
187        }
188
189        // Construct from Rel<T> entries. Does not contain an addend.
190        macro_rules! rel_entry {
191            ($entry:ident) => {
192                None
193            };
194        }
195
196        // Construct from Rela<T> entries. Contains an addend.
197        macro_rules! rela_entry {
198            ($entry:ident) => {
199                Some($entry.get_addend() as u64)
200            };
201        }
202
203        // If either section exists apply the relocations
204        relocation_section.map_or(Ok(()), |rela_section_dyn| {
205            let data = rela_section_dyn.get_data(&self.file)?;
206            match data {
207                SectionData::Rel32(rel_entries) => {
208                    iter_entries_and_relocate!(rel_entries, rel_entry);
209                }
210                SectionData::Rela32(rela_entries) => {
211                    iter_entries_and_relocate!(rela_entries, rela_entry);
212                }
213                SectionData::Rel64(rel_entries) => {
214                    iter_entries_and_relocate!(rel_entries, rel_entry);
215                }
216                SectionData::Rela64(rela_entries) => {
217                    iter_entries_and_relocate!(rela_entries, rela_entry);
218                }
219                _ => return Err(ElfLoaderErr::UnsupportedSectionData),
220            }
221            Ok(())
222        })
223    }
224
225    /// Processes a dynamic header section.
226    ///
227    /// This section contains mostly entry points to other section headers (like relocation).
228    /// At the moment this just does sanity checking for relocation later.
229    ///
230    /// A human readable version of the dynamic section is best obtained with `readelf -d <binary>`.
231    fn parse_dynamic<'a>(
232        file: &ElfFile,
233        dynamic_header: &'a ProgramHeader<'a>,
234    ) -> Result<Option<DynamicInfo>, ElfLoaderErr> {
235        // Walk through the dynamic program header and find the rela and sym_tab section offsets:
236        let segment = dynamic_header.get_data(file)?;
237
238        // Init result
239        let mut info = DynamicInfo {
240            flags1: Default::default(),
241            rela: 0,
242            rela_size: 0,
243        };
244
245        // Each entry/section is parsed for the same information currently
246        macro_rules! parse_entry_tags {
247            ($info:ident, $entry:ident, $tag:ident) => {
248                match $tag {
249                    // Trace required libs
250                    Tag::Needed => {}
251
252                    // Rel<T>
253                    Tag::Rel => $info.rela = $entry.get_ptr()?.into(),
254                    Tag::RelSize => $info.rela_size = $entry.get_val()?.into(),
255
256                    // Rela<T>
257                    Tag::Rela => $info.rela = $entry.get_ptr()?.into(),
258                    Tag::RelaSize => $info.rela_size = $entry.get_val()?.into(),
259                    Tag::Flags1 => {
260                        $info.flags1 =
261                            unsafe { DynamicFlags1::from_bits_unchecked($entry.get_val()? as _) };
262                    }
263                    _ => {}
264                }
265            };
266        }
267
268        // Helper macro to iterate all entries
269        macro_rules! iter_entries_and_parse {
270            ($info:ident, $dyn_entries:expr) => {
271                for dyn_entry in $dyn_entries {
272                    let tag = dyn_entry.get_tag()?;
273                    parse_entry_tags!($info, dyn_entry, tag);
274                }
275            };
276        }
277
278        match segment {
279            SegmentData::Dynamic32(dyn_entries) => {
280                iter_entries_and_parse!(info, dyn_entries);
281            }
282            SegmentData::Dynamic64(dyn_entries) => {
283                iter_entries_and_parse!(info, dyn_entries);
284            }
285            _ => {
286                return Err(ElfLoaderErr::UnsupportedSectionData);
287            }
288        };
289
290        Ok(Some(info))
291    }
292
293    /// Processing the program headers and issue commands to loader.
294    ///
295    /// Will tell loader to create space in the address space / region where the
296    /// header is supposed to go, then copy it there, and finally relocate it.
297    pub fn load(&self, loader: &mut dyn ElfLoader) -> Result<(), ElfLoaderErr> {
298        self.is_loadable()?;
299
300        loader.allocate(self.iter_loadable_headers())?;
301
302        // Load all headers
303        for header in self.file.program_iter() {
304            let raw = match header {
305                Ph32(inner) => inner.raw_data(&self.file),
306                Ph64(inner) => inner.raw_data(&self.file),
307            };
308            let typ = header.get_type()?;
309            match typ {
310                Type::Load => {
311                    loader.load(header.flags(), header.virtual_addr(), raw)?;
312                }
313                Type::Tls => {
314                    loader.tls(
315                        header.virtual_addr(),
316                        header.file_size(),
317                        header.mem_size(),
318                        header.align(),
319                    )?;
320                }
321                _ => {} // skip for now
322            }
323        }
324
325        // Relocate headers
326        self.maybe_relocate(loader)?;
327
328        // Process .data.rel.ro
329        for header in self.file.program_iter() {
330            if header.get_type()? == Type::GnuRelro {
331                loader.make_readonly(header.virtual_addr(), header.mem_size() as usize)?
332            }
333        }
334
335        Ok(())
336    }
337
338    fn iter_loadable_headers(&self) -> LoadableHeaders {
339        // Trying to determine loadeable headers
340        fn select_load(pheader: &ProgramHeader) -> bool {
341            match pheader {
342                Ph32(header) => header
343                    .get_type()
344                    .map(|typ| typ == Type::Load)
345                    .unwrap_or(false),
346                Ph64(header) => header
347                    .get_type()
348                    .map(|typ| typ == Type::Load)
349                    .unwrap_or(false),
350            }
351        }
352
353        // Create an iterator (well filter really) that has all loadeable
354        // headers and pass it to the loader
355        // TODO: This is pretty ugly, maybe we can do something with impl Trait?
356        // https://stackoverflow.com/questions/27535289/what-is-the-correct-way-to-return-an-iterator-or-any-other-trait
357        self.file.program_iter().filter(select_load)
358    }
359}