Skip to content

Commit 230570f

Browse files
authored
Merge pull request #660 from workingjubilee/dont-unsoundly-iterate-phdr
Revise `dl_iterate_phdr` callback to be sound-ish
2 parents 7d062c6 + 153f510 commit 230570f

File tree

1 file changed

+36
-17
lines changed

1 file changed

+36
-17
lines changed

src/symbolize/gimli/libs_dl_iterate_phdr.rs

+36-17
Original file line numberDiff line numberDiff line change
@@ -35,31 +35,50 @@ fn infer_current_exe(base_addr: usize) -> OsString {
3535
env::current_exe().map(|e| e.into()).unwrap_or_default()
3636
}
3737

38-
// `info` should be a valid pointers.
39-
// `vec` should be a valid pointer to a `std::Vec`.
38+
/// # Safety
39+
/// `info` must be a valid pointer.
40+
/// `vec` must be a valid pointer to `Vec<Library>`
41+
#[forbid(unsafe_op_in_unsafe_fn)]
4042
unsafe extern "C" fn callback(
4143
info: *mut libc::dl_phdr_info,
4244
_size: libc::size_t,
4345
vec: *mut libc::c_void,
4446
) -> libc::c_int {
45-
let info = &*info;
46-
let libs = &mut *vec.cast::<Vec<Library>>();
47-
let is_main_prog = info.dlpi_name.is_null() || *info.dlpi_name == 0;
48-
let name = if is_main_prog {
49-
// The man page for dl_iterate_phdr says that the first object visited by
50-
// callback is the main program; so the first time we encounter a
51-
// nameless entry, we can assume its the main program and try to infer its path.
52-
// After that, we cannot continue that assumption, and we use an empty string.
53-
if libs.is_empty() {
54-
infer_current_exe(info.dlpi_addr as usize)
55-
} else {
47+
// SAFETY: We are guaranteed these fields:
48+
let dlpi_addr = unsafe { (*info).dlpi_addr };
49+
let dlpi_name = unsafe { (*info).dlpi_name };
50+
let dlpi_phdr = unsafe { (*info).dlpi_phdr };
51+
let dlpi_phnum = unsafe { (*info).dlpi_phnum };
52+
// SAFETY: We assured this.
53+
let libs = unsafe { &mut *vec.cast::<Vec<Library>>() };
54+
// most implementations give us the main program first
55+
let is_main = libs.is_empty();
56+
// we may be statically linked, which means we are main and mostly one big blob of code
57+
let is_static = dlpi_addr == 0;
58+
// sometimes we get a null or 0-len CStr, based on libc's whims, but these mean the same thing
59+
let no_given_name = dlpi_name.is_null()
60+
// SAFETY: we just checked for null
61+
|| unsafe { *dlpi_name == 0 };
62+
let name = if is_static {
63+
// don't try to look up our name from /proc/self/maps, it'll get silly
64+
env::current_exe().unwrap_or_default().into_os_string()
65+
} else if is_main && no_given_name {
66+
infer_current_exe(dlpi_addr as usize)
67+
} else {
68+
// this fallback works even if we are main, because some platforms give the name anyways
69+
if dlpi_name.is_null() {
5670
OsString::new()
71+
} else {
72+
// SAFETY: we just checked for nullness
73+
OsStr::from_bytes(unsafe { CStr::from_ptr(dlpi_name) }.to_bytes()).to_owned()
5774
}
75+
};
76+
let headers = if dlpi_phdr.is_null() || dlpi_phnum == 0 {
77+
&[]
5878
} else {
59-
let bytes = CStr::from_ptr(info.dlpi_name).to_bytes();
60-
OsStr::from_bytes(bytes).to_owned()
79+
// SAFETY: We just checked for nullness or 0-len slices
80+
unsafe { slice::from_raw_parts(dlpi_phdr, dlpi_phnum as usize) }
6181
};
62-
let headers = slice::from_raw_parts(info.dlpi_phdr, info.dlpi_phnum as usize);
6382
libs.push(Library {
6483
name,
6584
segments: headers
@@ -69,7 +88,7 @@ unsafe extern "C" fn callback(
6988
stated_virtual_memory_address: (*header).p_vaddr as usize,
7089
})
7190
.collect(),
72-
bias: info.dlpi_addr as usize,
91+
bias: dlpi_addr as usize,
7392
});
7493
0
7594
}

0 commit comments

Comments
 (0)