Skip to content

Commit 046a431

Browse files
committed
Use memory sizes and offsets, not file sizes and file offsets on Linux
In the `SharedLibrary::id` method on Linux, we were mistakenly using file sizes and offsets when searching through the notes structures in the PT_NOTE segment. This corrects that issue to use the fields in phdr et al that are explicitly for memory offsets and sizes. Fixes #50
1 parent b0fc989 commit 046a431

File tree

2 files changed

+112
-48
lines changed

2 files changed

+112
-48
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ rust:
1212
- nightly
1313
- beta
1414
- stable
15-
- 1.31.0
15+
- 1.34.0
1616
cache: cargo
1717

1818
addons:

src/linux/mod.rs

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::borrow::Cow;
1111
use std::env::current_exe;
1212
use std::ffi::{CStr, CString, OsStr};
1313
use std::fmt;
14+
use std::iter;
1415
use std::marker::PhantomData;
1516
use std::mem;
1617
use std::os::unix::ffi::OsStrExt;
@@ -27,7 +28,12 @@ type Phdr = libc::Elf64_Phdr;
2728

2829
const NT_GNU_BUILD_ID: u32 = 3;
2930

30-
struct Nhdr32 {
31+
// Normally we would use `Elf32_Nhdr` on 32-bit platforms and `Elf64_Nhdr` on
32+
// 64-bit platforms. However, in practice it seems that only `Elf32_Nhdr` is
33+
// used, and reading through binutil's `readelf` source confirms this.
34+
#[repr(C)]
35+
#[derive(Debug, Clone, Copy)]
36+
struct Nhdr {
3137
pub n_namesz: libc::Elf32_Word,
3238
pub n_descsz: libc::Elf32_Word,
3339
pub n_type: libc::Elf32_Word,
@@ -41,14 +47,100 @@ pub struct Segment<'a> {
4147
}
4248

4349
impl<'a> Segment<'a> {
50+
fn phdr(&self) -> &'a Phdr {
51+
unsafe { self.phdr.as_ref().unwrap() }
52+
}
53+
54+
/// You must pass this segment's `SharedLibrary` or else this is wild UB.
55+
unsafe fn data(&self, shlib: &SharedLibrary<'a>) -> &'a [u8] {
56+
let phdr = self.phdr();
57+
let avma = (shlib.addr as usize).wrapping_add(phdr.p_vaddr as usize);
58+
slice::from_raw_parts(avma as *const u8, phdr.p_memsz as usize)
59+
}
60+
4461
fn is_load(&self) -> bool {
45-
unsafe {
46-
let hdr = self.phdr.as_ref().unwrap();
47-
match hdr.p_type {
48-
libc::PT_LOAD => true,
49-
_ => false,
62+
self.phdr().p_type == libc::PT_LOAD
63+
}
64+
65+
fn is_note(&self) -> bool {
66+
self.phdr().p_type == libc::PT_NOTE
67+
}
68+
69+
/// Parse the contents of a `PT_NOTE` segment.
70+
///
71+
/// Returns a triple of
72+
///
73+
/// 1. The `NT_*` note type.
74+
/// 2. The note name.
75+
/// 3. The note descriptor payload.
76+
///
77+
/// You must pass this segment's `SharedLibrary` or else this is wild UB.
78+
unsafe fn notes(
79+
&self,
80+
shlib: &SharedLibrary<'a>,
81+
) -> impl Iterator<Item = (libc::Elf32_Word, &'a [u8], &'a [u8])> {
82+
assert!(self.is_note());
83+
84+
// `man 5 readelf` says that all of the `Nhdr`, name, and descriptor are
85+
// always 4-byte aligned, but we copy this alignment behavior from
86+
// `readelf` since that seems to match reality in practice.
87+
let alignment = std::cmp::max(self.phdr().p_align as usize, 4);
88+
let align_up = move |data: &'a [u8]| {
89+
if alignment != 4 && alignment != 8 {
90+
return None;
5091
}
51-
}
92+
93+
let ptr = data.as_ptr() as usize;
94+
let alignment_minus_one = alignment - 1;
95+
let aligned_ptr = ptr.checked_add(alignment_minus_one)? & !alignment_minus_one;
96+
let diff = aligned_ptr - ptr;
97+
if data.len() < diff {
98+
None
99+
} else {
100+
Some(&data[diff..])
101+
}
102+
};
103+
104+
let mut data = self.data(shlib);
105+
106+
iter::from_fn(move || {
107+
assert_eq!(data.as_ptr() as usize % alignment, 0);
108+
109+
// Each entry in a `PT_NOTE` segment begins with a
110+
// fixed-size header `Nhdr`.
111+
let nhdr_size = mem::size_of::<Nhdr>();
112+
let nhdr = try_split_at(&mut data, nhdr_size)?;
113+
let nhdr = (nhdr.as_ptr() as *const Nhdr).as_ref().unwrap();
114+
115+
// No need to `align_up` after the `Nhdr`.
116+
debug_assert_eq!(nhdr_size % alignment, 0);
117+
118+
// It is followed by a name of size `n_namesz`.
119+
let name_size = nhdr.n_namesz as usize;
120+
let name = try_split_at(&mut data, name_size)?;
121+
122+
// And after that is the note's (aligned) descriptor payload of size
123+
// `n_descsz`.
124+
data = align_up(data)?;
125+
let desc_size = nhdr.n_descsz as usize;
126+
let desc = try_split_at(&mut data, desc_size)?;
127+
128+
// Align the data for the next `Nhdr`.
129+
data = align_up(data)?;
130+
131+
Some((nhdr.n_type, name, desc))
132+
})
133+
.fuse()
134+
}
135+
}
136+
137+
fn try_split_at<'a>(data: &mut &'a [u8], index: usize) -> Option<&'a [u8]> {
138+
if data.len() < index {
139+
None
140+
} else {
141+
let (head, tail) = data.split_at(index);
142+
*data = tail;
143+
Some(head)
52144
}
53145
}
54146

@@ -204,6 +296,10 @@ impl<'a> SharedLibrary<'a> {
204296
}
205297
}
206298
}
299+
300+
fn note_segments(&self) -> impl Iterator<Item = Segment<'a>> {
301+
self.segments().filter(|s| s.is_note())
302+
}
207303
}
208304

209305
impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
@@ -216,46 +312,14 @@ impl<'a> SharedLibraryTrait for SharedLibrary<'a> {
216312
}
217313

218314
fn id(&self) -> Option<SharedLibraryId> {
219-
fn align(alignment: usize, offset: &mut usize) {
220-
let diff = *offset % alignment;
221-
if diff != 0 {
222-
*offset += alignment - diff;
223-
}
224-
}
225-
226-
unsafe {
227-
for segment in self.segments() {
228-
let phdr = segment.phdr.as_ref().unwrap();
229-
if phdr.p_type != libc::PT_NOTE {
230-
continue;
231-
}
232-
233-
let mut alignment = phdr.p_align as usize;
234-
// same logic as in gimli which took it from readelf
235-
if alignment < 4 {
236-
alignment = 4;
237-
} else if alignment != 4 && alignment != 8 {
238-
continue;
239-
}
240-
241-
let mut offset = phdr.p_offset as usize;
242-
let end = offset + phdr.p_filesz as usize;
243-
244-
while offset < end {
245-
// we always use an nhdr32 here as 64bit notes have not
246-
// been observed in practice.
247-
let nhdr = &*((self.addr as usize + offset) as *const Nhdr32);
248-
offset += mem::size_of_val(nhdr);
249-
offset += nhdr.n_namesz as usize;
250-
align(alignment, &mut offset);
251-
let value =
252-
slice::from_raw_parts(self.addr.add(offset), nhdr.n_descsz as usize);
253-
offset += nhdr.n_descsz as usize;
254-
align(alignment, &mut offset);
255-
256-
if nhdr.n_type as u32 == NT_GNU_BUILD_ID {
257-
return Some(SharedLibraryId::GnuBuildId(value.to_vec()));
258-
}
315+
// Search for `PT_NOTE` segments, containing auxilliary information.
316+
// Such segments contain a series of "notes" and one kind of note is
317+
// `NT_GNU_BUILD_ID`, whose payload contains a unique identifier
318+
// generated by the linker. Return the first one we find, if any.
319+
for segment in self.note_segments() {
320+
for (note_type, note_name, note_descriptor) in unsafe { segment.notes(self) } {
321+
if note_type == NT_GNU_BUILD_ID && note_name == b"GNU\0" {
322+
return Some(SharedLibraryId::GnuBuildId(note_descriptor.to_vec()));
259323
}
260324
}
261325
}

0 commit comments

Comments
 (0)