Skip to content

Commit f07011b

Browse files
Always treat dlsym returning NULL as an error
This simplifies the code somewhat. Also updates comments to reflect notes from reviw about thread-safety of `dlerror`.
1 parent e2326a1 commit f07011b

File tree

1 file changed

+24
-31
lines changed

1 file changed

+24
-31
lines changed

src/librustc_metadata/dynamic_lib.rs

+24-31
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,16 @@ mod dl {
5454
use std::ffi::{CString, OsStr};
5555
use std::os::unix::prelude::*;
5656

57-
// `dlerror` is process global, so we can only allow a single thread at a
58-
// time to call `dlsym` and `dlopen` if we want to check the error message.
57+
// As of the 2017 revision of the POSIX standard (IEEE 1003.1-2017), it is
58+
// implementation-defined whether `dlerror` is thread-safe (in which case it returns the most
59+
// recent error in the calling thread) or not thread-safe (in which case it returns the most
60+
// recent error in *any* thread).
61+
//
62+
// There's no easy way to tell what strategy is used by a given POSIX implementation, so we
63+
// lock around all calls that can modify `dlerror` in this module lest we accidentally read an
64+
// error from a different thread. This is bulletproof when we are the *only* code using the
65+
// dynamic library APIs at a given point in time. However, it's still possible for us to race
66+
// with other code (see #74469) on platforms where `dlerror` is not thread-safe.
5967
mod error {
6068
use std::ffi::CStr;
6169
use std::lazy::SyncLazy;
@@ -97,51 +105,36 @@ mod dl {
97105
return Ok(ret);
98106
}
99107

100-
// A NULL return from `dlopen` indicates that an error has
101-
// definitely occurred, so if nothing is in `dlerror`, we are
102-
// racing with another thread that has stolen our error message.
108+
// A NULL return from `dlopen` indicates that an error has definitely occurred, so if
109+
// nothing is in `dlerror`, we are racing with another thread that has stolen our error
110+
// message. See the explanation on the `dl::error` module for more information.
103111
dlerror.get().and_then(|()| Err("Unknown error".to_string()))
104112
}
105113

106114
pub(super) unsafe fn symbol(
107115
handle: *mut u8,
108116
symbol: *const libc::c_char,
109117
) -> Result<*mut u8, String> {
110-
// HACK(#74469): On some platforms, users observed foreign code
111-
// (specifically libc) invoking `dlopen`/`dlsym` in parallel with the
112-
// functions in this module. This is problematic because, according to
113-
// the POSIX API documentation, `dlerror` must be called to determine
114-
// whether `dlsym` succeeded. Unlike `dlopen`, a NULL return value may
115-
// indicate a successfully resolved symbol with an address of zero.
116-
//
117-
// Because symbols with address zero shouldn't occur in practice, we
118-
// treat them as errors on platforms with misbehaving libc
119-
// implementations.
120-
const DLSYM_NULL_IS_ERROR: bool = cfg!(target_os = "illumos");
121-
122118
let mut dlerror = error::lock();
123119

124-
// No need to flush `dlerror` if we aren't using it to determine whether
125-
// the subsequent call to `dlsym` succeeded. If an error occurs, any
126-
// stale value will be overwritten.
127-
if !DLSYM_NULL_IS_ERROR {
128-
dlerror.clear();
129-
}
120+
// Unlike `dlopen`, it's possible for `dlsym` to return NULL without overwriting `dlerror`.
121+
// Because of this, we clear `dlerror` before calling `dlsym` to avoid picking up a stale
122+
// error message by accident.
123+
dlerror.clear();
130124

131125
let ret = libc::dlsym(handle as *mut libc::c_void, symbol) as *mut u8;
132126

133-
// A non-NULL return value *always* indicates success. There's no need
134-
// to check `dlerror`.
135127
if !ret.is_null() {
136128
return Ok(ret);
137129
}
138130

139-
match dlerror.get() {
140-
Ok(()) if DLSYM_NULL_IS_ERROR => Err("Unknown error".to_string()),
141-
Ok(()) => Ok(ret),
142-
143-
Err(msg) => Err(msg),
144-
}
131+
// If `dlsym` returns NULL but there is nothing in `dlerror` it means one of two things:
132+
// - We tried to load a symbol mapped to address 0. This is not technically an error but is
133+
// unlikely to occur in practice and equally unlikely to be handled correctly by calling
134+
// code. Therefore we treat it as an error anyway.
135+
// - An error has occurred, but we are racing with another thread that has stolen our error
136+
// message. See the explanation on the `dl::error` module for more information.
137+
dlerror.get().and_then(|()| Err("Tried to load symbol mapped to address 0".to_string()))
145138
}
146139

147140
pub(super) unsafe fn close(handle: *mut u8) {

0 commit comments

Comments
 (0)