@@ -54,8 +54,16 @@ mod dl {
54
54
use std:: ffi:: { CString , OsStr } ;
55
55
use std:: os:: unix:: prelude:: * ;
56
56
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.
59
67
mod error {
60
68
use std:: ffi:: CStr ;
61
69
use std:: lazy:: SyncLazy ;
@@ -97,51 +105,36 @@ mod dl {
97
105
return Ok ( ret) ;
98
106
}
99
107
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 .
103
111
dlerror. get ( ) . and_then ( |( ) | Err ( "Unknown error" . to_string ( ) ) )
104
112
}
105
113
106
114
pub ( super ) unsafe fn symbol (
107
115
handle : * mut u8 ,
108
116
symbol : * const libc:: c_char ,
109
117
) -> 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
-
122
118
let mut dlerror = error:: lock ( ) ;
123
119
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 ( ) ;
130
124
131
125
let ret = libc:: dlsym ( handle as * mut libc:: c_void , symbol) as * mut u8 ;
132
126
133
- // A non-NULL return value *always* indicates success. There's no need
134
- // to check `dlerror`.
135
127
if !ret. is_null ( ) {
136
128
return Ok ( ret) ;
137
129
}
138
130
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 ( ) ) )
145
138
}
146
139
147
140
pub ( super ) unsafe fn close ( handle : * mut u8 ) {
0 commit comments