@@ -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