-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathrelocate.rs
More file actions
395 lines (344 loc) · 16.3 KB
/
relocate.rs
File metadata and controls
395 lines (344 loc) · 16.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
//! Program relocation.
//!
//! If we're a static PIE binary, we don't have a dynamic linker to perform
//! dynamic (startup-time) relocations for us, so we have to do it ourselves.
//! This is awkward, because it means we're running Rust code at a time when
//! the invariants that Rust code typically assume are not established yet.
//!
//! We use special functions implemented using `asm` to perform the actual
//! relocation memory accesses, so that Rust semantics don't have to be aware
//! of them.
//!
//! See the comments on the `relocate` function for additional special
//! considerations.
#![allow(clippy::cmp_null)]
use crate::arch::{
dynamic_table_addr, ehdr_addr, relocation_load, relocation_mprotect_readonly, relocation_store,
trap,
};
use core::ffi::c_void;
use core::mem;
use core::ptr::{null, null_mut};
use linux_raw_sys::auxvec::{AT_BASE, AT_ENTRY, AT_NULL, AT_PAGESZ};
use linux_raw_sys::elf::*;
// The Linux UAPI headers don't define the .relr types and consts yet.
#[allow(non_camel_case_types)]
type Elf_Relr = usize;
const DT_RELRSZ: usize = 35;
const DT_RELR: usize = 36;
#[cfg(debug_assertions)]
const DT_RELRENT: usize = 37;
// We have to override the debug_assert! family of macros to trap rather than
// panic as panicking doesn't work this early on. See the docs of [relocate]
// for more info.
#[cfg(debug_assertions)]
macro_rules! debug_assert_eq {
($l:expr, $r:expr) => {
if !($l == $r) {
trap();
}
};
}
/// Locate the dynamic (startup-time) relocations and perform them.
///
/// This is unsafer than unsafe. It's meant to be called at a time when Rust
/// code is running but before relocations have been performed. There are no
/// guarantees that this code won't segfault at any moment.
///
/// In practice, things work if we don't make any calls to functions outside
/// of this crate, not counting `inline(always)` functions. So we can do eg.
/// `x == null()` but we can't do `x.is_null()`, because `null` is
/// `inline(always)`, while `is_null` is not 🙃. And, it includes code
/// patterns that the compiler might implement as calls to functions like
/// `memset`.
///
/// Implicit calls to `memset` etc. could possibly be prevented by using
/// `#![no_builtins]`, however wouldn't fix the other problems, so we'd still
/// need to rely on testing the code to make sure it doesn't segfault in any
/// case. And, `#![no_builtins]` interferes with LTO, so we don't use it.
///
/// And, we have to avoid reading any static variables that contain addresses,
/// including any tables the compiler might implicitly emit.
///
/// And, we do all the relocation memory accesses using `asm`, as they happen
/// outside the Rust memory model. They read and write and `mprotect` memory
/// that Rust wouldn't think could be accessed.
///
/// And, we also need to take care to avoid panics as panicking will segfault
/// due to `core::fmt` making use of statics that need relocation. Even after
/// all relocations are done we still need to avoid panicking as libstd's panic
/// handler makes use of TLS, which won't be initialized until much later.
///
/// So yes, there's a reason this code is behind a feature flag.
#[cold]
pub(super) unsafe fn relocate(envp: *mut *mut u8) {
unsafe {
// Locate the AUX records we need.
let auxp = compute_auxp(envp);
// The page size, segment headers info, and runtime entry address.
let mut auxv_base = null_mut();
let mut auxv_page_size = 0;
let mut auxv_entry = null_mut();
// Look through the AUX records to find the segment headers, page size,
// and runtime entry address.
let mut current_aux = auxp;
loop {
let Elf_auxv_t { a_type, a_val } = *current_aux;
current_aux = current_aux.add(1);
match a_type as _ {
AT_BASE => auxv_base = a_val,
AT_PAGESZ => auxv_page_size = a_val.addr(),
AT_ENTRY => auxv_entry = a_val,
AT_NULL => break,
_ => (),
}
}
// There are four cases to consider here:
//
// 1. Static executable.
// This is the trivial case. No relocations necessary.
// 2. Static pie executable.
// We need to relocate ourself. `AT_PHDR` points to our own program
// headers and `AT_ENTRY` to our own entry point.
// 3. Dynamic pie executable with external dynamic linker.
// We don't need to relocate ourself as the dynamic linker has already
// done this. `AT_PHDR` points to our own program headers and `AT_ENTRY`
// to our own entry point. `AT_BASE` contains the relocation offset of
// the dynamic linker.
// 4. Shared library acting as dynamic linker.
// We do need to relocate ourself. `AT_PHDR` doesn't point to our own
// program headers and `AT_ENTRY` doesn't point to our own entry point.
// `AT_BASE` contains our own relocation offset.
if load_static_start() == auxv_entry.addr() {
// This is case 1) or case 3). If `AT_BASE` doesn't exist, then we are
// already loaded at our static address despite the lack of any dynamic
// linker. As such it would be case 1). If `AT_BASE` does exist, we have
// already been relocated by the dynamic linker, which is case 3).
// In either case there is no need to do any relocations.
return;
}
let the_ehdr = &*ehdr_addr();
let base = if auxv_base == null_mut() {
// Obtain the static address of `_start`, which is recorded in the
// entry field of the ELF header.
let static_start = the_ehdr.e_entry;
// This is case 2) as without dynamic linker `AT_BASE` doesn't exist
// and we have already excluded case 1) above.
auxv_entry.wrapping_sub(static_start)
} else {
// This is case 4) as `AT_BASE` indicates that there is a dynamic
// linker, yet we are not already relocated by a dynamic linker.
// `AT_BASE` contains the relocation offset of the dynamic linker.
auxv_base
};
let offset = base.addr();
// This is case 2) or 4). We need to do all `R_RELATIVE` relocations.
// There should be no other kind of relocation because we are either a
// static PIE binary or a dynamic linker compiled with `-Bsymbolic`.
// Compute the dynamic address of `_DYNAMIC`.
let dynv = dynamic_table_addr();
// Rela tables contain `Elf_Rela` elements which have an
// `r_addend` field.
let mut rela_ptr: *const Elf_Rela = null();
let mut rela_total_size = 0;
// Rel tables contain `Elf_Rel` elements which lack an
// `r_addend` field and store the addend in the memory to be
// relocated.
let mut rel_ptr: *const Elf_Rel = null();
let mut rel_total_size = 0;
// Relr tables contain `Elf_Relr` elements which are a compact
// way of representing relative relocations.
let mut relr_ptr: *const Elf_Relr = null();
let mut relr_total_size = 0;
// Look through the `Elf_Dyn` entries to find the location and
// size of the relocation table(s).
let mut current_dyn: *const Elf_Dyn = dynv;
loop {
let Elf_Dyn { d_tag, d_un } = &*current_dyn;
current_dyn = current_dyn.add(1);
match *d_tag {
// We found a rela table. As above, model this as
// `with_exposed_provenance`.
DT_RELA => rela_ptr = base.byte_add(d_un.d_ptr).cast::<Elf_Rela>(),
DT_RELASZ => rela_total_size = d_un.d_val as usize,
#[cfg(debug_assertions)]
DT_RELAENT => debug_assert_eq!(d_un.d_val as usize, size_of::<Elf_Rela>()),
// We found a rel table. As above, model this as
// `with_exposed_provenance`.
DT_REL => rel_ptr = base.byte_add(d_un.d_ptr).cast::<Elf_Rel>(),
DT_RELSZ => rel_total_size = d_un.d_val as usize,
#[cfg(debug_assertions)]
DT_RELENT => debug_assert_eq!(d_un.d_val as usize, size_of::<Elf_Rel>()),
// We found a relr table. As above, model this as
// `with_exposed_provenance`.
DT_RELR => relr_ptr = base.byte_add(d_un.d_ptr).cast::<Elf_Relr>(),
DT_RELRSZ => relr_total_size = d_un.d_val as usize,
#[cfg(debug_assertions)]
DT_RELRENT => debug_assert_eq!(d_un.d_val as usize, size_of::<Elf_Relr>()),
// End of the Dynamic section
DT_NULL => break,
_ => (),
}
}
// Perform the rela relocations.
let mut current_rela = rela_ptr;
let rela_end = current_rela.byte_add(rela_total_size);
while current_rela != rela_end {
let rela = &*current_rela;
current_rela = current_rela.add(1);
// Calculate the location the relocation will apply at.
let reloc_addr = rela.r_offset.wrapping_add(offset);
// Perform the rela relocation.
match rela.type_() {
R_RELATIVE => {
let addend = rela.r_addend;
let reloc_value = addend.wrapping_add(offset);
relocation_store(reloc_addr, reloc_value);
}
// Trap the process without panicking as panicking requires
// relocations to be performed first.
_ => trap(),
}
}
// Perform the rel relocations.
let mut current_rel = rel_ptr;
let rel_end = current_rel.byte_add(rel_total_size);
while current_rel != rel_end {
let rel = &*current_rel;
current_rel = current_rel.add(1);
// Calculate the location the relocation will apply at.
let reloc_addr = rel.r_offset.wrapping_add(offset);
// Perform the rel relocation.
match rel.type_() {
R_RELATIVE => {
let addend = relocation_load(reloc_addr);
let reloc_value = addend.wrapping_add(offset);
relocation_store(reloc_addr, reloc_value);
}
// Trap the process without panicking as panicking requires
// relocations to be performed first.
_ => trap(),
}
}
// Perform the relr relocations.
let mut current_relr = relr_ptr;
let relr_end = current_relr.byte_add(relr_total_size);
let mut reloc_addr = 0;
while current_relr != relr_end {
let mut entry = *current_relr;
current_relr = current_relr.add(1);
if entry & 1 == 0 {
// Entry encodes offset to relocate; calculate the location
// the relocation will apply at.
reloc_addr = offset + entry;
// Perform the relr relocation.
let addend = relocation_load(reloc_addr);
let reloc_value = addend.wrapping_add(offset);
relocation_store(reloc_addr, reloc_value);
// Advance relocation location.
reloc_addr += mem::size_of::<usize>();
} else {
// Entry encodes bitmask with locations to relocate; apply each entry.
let mut i = 0;
loop {
// Shift to next item in the bitmask.
entry >>= 1;
if entry == 0 {
// No more entries left in the bitmask; early exit.
break;
}
if entry & 1 != 0 {
// Perform the relr relocation.
let addend = relocation_load(reloc_addr + i * mem::size_of::<usize>());
let reloc_value = addend.wrapping_add(offset);
relocation_store(reloc_addr + i * mem::size_of::<usize>(), reloc_value);
}
i += 1;
}
// Advance relocation location.
reloc_addr += (mem::size_of::<usize>() * 8 - 1) * mem::size_of::<usize>();
}
}
// FIXME split function into two here with a hint::black_box around the
// function pointer to prevent the compiler from moving code between the
// functions.
// Check that the page size is a power of two.
debug_assert!(auxv_page_size.is_power_of_two());
// This code doesn't rely on the offset being page aligned, but it is
// useful to check to make sure we computed it correctly.
debug_assert_eq!(offset & (auxv_page_size - 1), 0);
// Check that relocation did its job. Do the same static start
// computation we did earlier; this time it should match the dynamic
// address.
// AT_ENTRY points to the main executable's entry point rather than our
// entry point when AT_BASE is not zero and thus a dynamic linker is in
// use. In this case the assertion would fail.
if auxv_base == null_mut() {
debug_assert_eq!(load_static_start(), auxv_entry.addr());
}
// Finally, look through the static segment headers (phdrs) to find the
// the relro description if present. Also do a debug assertion that
// the dynv argument matches the PT_DYNAMIC segment.
// The location and size of the `relro` region.
let mut relro = 0;
let mut relro_size = 0;
let phentsize = the_ehdr.e_phentsize as usize;
let mut current_phdr = base.byte_add(the_ehdr.e_phoff).cast::<Elf_Phdr>();
let phdrs_end = current_phdr.byte_add(the_ehdr.e_phnum as usize * phentsize);
while current_phdr != phdrs_end {
let phdr = &*current_phdr;
current_phdr = current_phdr.byte_add(phentsize);
match phdr.p_type {
#[cfg(debug_assertions)]
PT_DYNAMIC => {
assert_eq!(dynv, base.byte_add(phdr.p_vaddr).cast::<Elf_Dyn>());
}
PT_GNU_RELRO => {
// A relro description is present. Make a note of it so that we
// can mark the memory readonly after relocations are done.
relro = phdr.p_vaddr;
relro_size = phdr.p_memsz;
}
_ => (),
}
}
// If we saw a relro description, mark the memory readonly.
if relro_size != 0 {
let mprotect_addr = relro.wrapping_add(offset) & auxv_page_size.wrapping_neg();
relocation_mprotect_readonly(mprotect_addr, relro_size);
}
}
}
/// Compute the address of the AUX table.
unsafe fn compute_auxp(envp: *mut *mut u8) -> *const Elf_auxv_t {
unsafe {
// Locate the AUX records we need. We don't use rustix to do this because
// that would involve calling a function in another crate.
let mut auxp = envp;
// Don't use `is_null` because that's not `inline(always)`.
while *auxp != null_mut() {
auxp = auxp.add(1);
}
auxp.add(1).cast()
}
}
/// Load the address of `_start` from static memory.
///
/// This function contains a static variable initialized with the address of
/// the `_start` function. This requires an `R_RELATIVE` relocation, because
/// it requires an absolute address exist in memory, rather than being able
/// to use PC-relative addressing. This allows us to tell whether relocation
/// has already been done or whether we need to do it.
///
/// This returns a `usize` because we don't dereference the address; we just
/// use it for address computation.
fn load_static_start() -> usize {
// Initialize a static variable with the address of `_start`.
struct StaticStart(*const c_void);
unsafe impl Sync for StaticStart {}
static STATIC_START: StaticStart = StaticStart(crate::arch::_start as *const c_void);
// Use `relocation_load` to do the load because the optimizer won't have
// any idea what we're up to.
let static_start_addr: *const *const c_void = &STATIC_START.0;
unsafe { relocation_load(static_start_addr.addr()) }
}