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#[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 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 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 let find_dyn_value = |tag| dynamic.iter().find(|d| d.d_tag == tag).map(|d| d.d_val());
142
143 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 reloc: "DF_TEXTREL".to_string(),
165 }
166 .into());
167 }
168 }
169 debug!("{}: relocation flags: {:?} {:?}", lib, flags, flags_1);
170
171 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 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 if let Some((rel, kind, sz)) = jmprels {
216 let ent = match kind as i64 {
217 DT_REL => 2, DT_RELA => 3, _ => {
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 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 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 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}