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#[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 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 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 let find_dyn_value = |tag| dynamic.iter().find(|d| d.d_tag == tag).map(|d| d.d_val());
146
147 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 reloc: "DF_TEXTREL".into(),
169 }
170 .into());
171 }
172 }
173 debug!("{}: relocation flags: {:?} {:?}", lib, flags, flags_1);
174
175 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 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 if let Some((rel, kind, sz)) = jmprels {
223 let ent = match kind as i64 {
224 DT_REL => 2, DT_RELA => 3, _ => {
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 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 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 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}