dynlink/context/
relocate.rs

1use std::{collections::HashSet, mem::size_of};
2
3use elf::{
4    abi::{
5        DF_TEXTREL, DT_FLAGS, DT_FLAGS_1, DT_JMPREL, DT_PLTGOT, DT_PLTREL, DT_PLTRELSZ, DT_REL,
6        DT_RELA, DT_RELAENT, DT_RELASZ, DT_RELENT, DT_RELSZ,
7    },
8    endian::NativeEndian,
9    parse::{ParseAt, ParsingIterator},
10    relocation::{Rel, Rela},
11    string_table::StringTable,
12    symbol::SymbolTable,
13};
14use tracing::{debug, error, trace};
15
16use super::{Context, Library};
17use crate::{
18    library::{LibraryId, RelocState},
19    DynlinkError, DynlinkErrorKind,
20};
21
22// A relocation is either a REL type or a RELA type. The only difference is that
23// the RELA type contains an addend (used in the reloc calculations below).
24#[derive(Debug)]
25pub(crate) enum EitherRel {
26    Rel(Rel),
27    Rela(Rela),
28}
29
30impl EitherRel {
31    pub fn r_type(&self) -> u32 {
32        match self {
33            EitherRel::Rel(r) => r.r_type,
34            EitherRel::Rela(r) => r.r_type,
35        }
36    }
37
38    pub fn addend(&self) -> i64 {
39        match self {
40            EitherRel::Rel(_) => 0,
41            EitherRel::Rela(r) => r.r_addend,
42        }
43    }
44
45    pub fn offset(&self) -> u64 {
46        match self {
47            EitherRel::Rel(r) => r.r_offset,
48            EitherRel::Rela(r) => r.r_offset,
49        }
50    }
51
52    pub fn sym(&self) -> u32 {
53        match self {
54            EitherRel::Rel(r) => r.r_sym,
55            EitherRel::Rela(r) => r.r_sym,
56        }
57    }
58}
59
60impl Context {
61    pub(crate) fn get_parsing_iter<P: ParseAt>(
62        &self,
63        start: *const u8,
64        ent: usize,
65        sz: usize,
66    ) -> Option<ParsingIterator<'_, NativeEndian, P>> {
67        P::validate_entsize(elf::file::Class::ELF64, ent).ok()?;
68        let iter = ParsingIterator::new(NativeEndian, elf::file::Class::ELF64, unsafe {
69            core::slice::from_raw_parts(start, sz)
70        });
71        Some(iter)
72    }
73
74    #[allow(clippy::too_many_arguments)]
75    fn process_rels(
76        &self,
77        lib: &Library,
78        start: *const u8,
79        ent: usize,
80        sz: usize,
81        name: &str,
82        strings: &StringTable,
83        syms: &SymbolTable<NativeEndian>,
84    ) -> Result<(), DynlinkError> {
85        debug!(
86            "{}: processing {} relocations (num = {})",
87            lib,
88            name,
89            sz / ent
90        );
91        // Try to parse the table as REL or RELA, according to ent size. If get_parsing_iter
92        // succeeds for a given relocation type, that's the correct one.
93        if let Some(rels) = self.get_parsing_iter(start, ent, sz) {
94            DynlinkError::collect(
95                DynlinkErrorKind::RelocationSectionFail {
96                    secname: "REL".to_string(),
97                    library: lib.name.clone(),
98                },
99                rels.map(|rel| self.do_reloc(lib, EitherRel::Rel(rel), strings, syms)),
100            )?;
101            Ok(())
102        } else if let Some(relas) = self.get_parsing_iter(start, ent, sz) {
103            DynlinkError::collect(
104                DynlinkErrorKind::RelocationSectionFail {
105                    secname: "RELA".to_string(),
106                    library: lib.name.clone(),
107                },
108                relas.map(|rela| self.do_reloc(lib, EitherRel::Rela(rela), strings, syms)),
109            )?;
110            Ok(())
111        } else {
112            let info = format!("reloc '{}' with entsz {}, size {}", name, ent, sz);
113            Err(DynlinkErrorKind::UnsupportedReloc {
114                library: lib.name.clone(),
115                reloc: info,
116            }
117            .into())
118        }
119    }
120
121    pub(crate) fn relocate_single(&mut self, lib_id: LibraryId) -> Result<(), DynlinkError> {
122        let lib = self.get_library(lib_id)?;
123        debug!("{}: relocating library", lib);
124        let elf = lib.get_elf()?;
125        let common = elf.find_common_data()?;
126        let dynamic = common
127            .dynamic
128            .ok_or_else(|| DynlinkErrorKind::MissingSection {
129                name: "dynamic".to_string(),
130            })?;
131
132        // Helper to lookup a single entry for a relocated pointer in the dynamic table.
133        let find_dyn_entry = |tag| {
134            dynamic
135                .iter()
136                .find(|d| d.d_tag == tag)
137                .map(|d| lib.laddr(d.d_ptr()))
138        };
139
140        // Helper to lookup a single value in the dynamic table.
141        let find_dyn_value = |tag| dynamic.iter().find(|d| d.d_tag == tag).map(|d| d.d_val());
142
143        // Many of the relocation tables are described in a similar way -- start, entry size, and
144        // table size (in bytes).
145        let find_dyn_rels = |tag, ent, sz| {
146            let rel = find_dyn_entry(tag);
147            let relent = find_dyn_value(ent);
148            let relsz = find_dyn_value(sz);
149            if let (Some(rel), Some(relent), Some(relsz)) = (rel, relent, relsz) {
150                Some((rel, relent, relsz))
151            } else {
152                None
153            }
154        };
155
156        let flags = find_dyn_value(DT_FLAGS);
157        let flags_1 = find_dyn_value(DT_FLAGS_1);
158        if let Some(flags) = flags {
159            if flags as i64 & DF_TEXTREL != 0 {
160                error!("{}: relocations within text not supported", lib);
161                return Err(DynlinkErrorKind::UnsupportedReloc {
162                    library: lib.name.to_string(),
163                    // TODO
164                    reloc: "DF_TEXTREL".to_string(),
165                }
166                .into());
167            }
168        }
169        debug!("{}: relocation flags: {:?} {:?}", lib, flags, flags_1);
170
171        // Lookup all the tables
172        let rels = find_dyn_rels(DT_REL, DT_RELENT, DT_RELSZ);
173        let relas = find_dyn_rels(DT_RELA, DT_RELAENT, DT_RELASZ);
174        let jmprels = find_dyn_rels(DT_JMPREL, DT_PLTREL, DT_PLTRELSZ);
175        let _pltgot: Option<*const u8> = find_dyn_entry(DT_PLTGOT);
176
177        let dynsyms = common
178            .dynsyms
179            .ok_or_else(|| DynlinkErrorKind::MissingSection {
180                name: "dynsyms".to_string(),
181            })?;
182        let dynsyms_str = common
183            .dynsyms_strs
184            .ok_or_else(|| DynlinkErrorKind::MissingSection {
185                name: "dynsyms_strs".to_string(),
186            })?;
187
188        // Process relocations
189        if let Some((rela, ent, sz)) = relas {
190            self.process_rels(
191                lib,
192                rela,
193                ent as usize,
194                sz as usize,
195                "RELA",
196                &dynsyms_str,
197                &dynsyms,
198            )?;
199        }
200
201        if let Some((rel, ent, sz)) = rels {
202            self.process_rels(
203                lib,
204                rel,
205                ent as usize,
206                sz as usize,
207                "REL",
208                &dynsyms_str,
209                &dynsyms,
210            )?;
211        }
212
213        // This one is a little special in that instead of an entry size, we are given a relocation
214        // type.
215        if let Some((rel, kind, sz)) = jmprels {
216            let ent = match kind as i64 {
217                DT_REL => 2,  // 2 usize long, according to ELF
218                DT_RELA => 3, // one extra usize for the addend
219                _ => {
220                    error!("failed to relocate {}: unknown PLTREL type", lib);
221                    return Err(DynlinkErrorKind::UnsupportedReloc {
222                        library: lib.name.clone(),
223                        reloc: "unknown PTREL type".to_string(),
224                    }
225                    .into());
226                }
227            } * size_of::<usize>();
228            self.process_rels(lib, rel, ent, sz as usize, "JMPREL", &dynsyms_str, &dynsyms)?;
229        }
230
231        Ok(())
232    }
233
234    fn relocate_recursive(&mut self, root_id: LibraryId) -> Result<(), DynlinkError> {
235        let lib = self.get_library(root_id)?;
236        let libname = lib.name.to_string();
237        match lib.reloc_state {
238            crate::library::RelocState::Unrelocated => {}
239            crate::library::RelocState::PartialRelocation => {
240                error!("{}: tried to relocate a failed library", lib);
241                return Err(DynlinkErrorKind::RelocationFail {
242                    library: lib.name.to_string(),
243                }
244                .into());
245            }
246            crate::library::RelocState::Relocated => {
247                trace!("{}: already relocated", lib);
248                return Ok(());
249            }
250        }
251
252        // We do this recursively instead of using a traversal, since we want to be able to prune
253        // nodes that we know we no longer need to relocate. But since the reloc state gets
254        // set at the end (so we can do this pruning), we'll need to track the visit states.
255        // In the end, this is depth-first postorder.
256        let deps = self
257            .library_deps
258            .neighbors_directed(root_id.0, petgraph::Direction::Outgoing)
259            .collect::<Vec<_>>();
260
261        let mut visit_state = HashSet::new();
262        visit_state.insert(root_id.0);
263        let rets = deps.into_iter().map(|dep_id| {
264            if !visit_state.contains(&dep_id) {
265                visit_state.insert(dep_id);
266                self.relocate_recursive(LibraryId(dep_id))
267            } else {
268                Ok(())
269            }
270        });
271
272        DynlinkError::collect(DynlinkErrorKind::DepsRelocFail { library: libname }, rets)?;
273
274        // Okay, deps are ready, let's reloc the root.
275        let lib = self.get_library_mut(root_id)?;
276        lib.reloc_state = RelocState::PartialRelocation;
277
278        let res = self.relocate_single(root_id);
279
280        let lib = self.get_library_mut(root_id)?;
281        if res.is_ok() {
282            lib.reloc_state = RelocState::Relocated;
283        } else {
284            lib.reloc_state = RelocState::PartialRelocation;
285        }
286        res
287    }
288
289    /// Iterate through all libraries and process relocations for any libraries that haven't yet
290    /// been relocated.
291    pub fn relocate_all(&mut self, root_id: LibraryId) -> Result<(), DynlinkError> {
292        let name = self.get_library(root_id)?.name.to_string();
293        self.relocate_recursive(root_id).map_err(|e| {
294            DynlinkError::new_collect(DynlinkErrorKind::RelocationFail { library: name }, vec![e])
295        })
296    }
297}