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