about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorDylan MacKenzie <ecstaticmorse@gmail.com>2020-08-25 12:02:21 -0700
committerDylan MacKenzie <ecstaticmorse@gmail.com>2020-08-25 12:02:21 -0700
commitf07011bad8a4ca3bd65ce1ed236f017f55108816 (patch)
tree4320f954d532974ace7fbc6df668bca1c1f1f18c /src
parente2326a1eecfe5562712649e8696c579bc919a9af (diff)
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`.
Diffstat (limited to 'src')
-rw-r--r--src/librustc_metadata/dynamic_lib.rs55
1 files changed, 24 insertions, 31 deletions
diff --git a/src/librustc_metadata/dynamic_lib.rs b/src/librustc_metadata/dynamic_lib.rs
index b49a1456046..8c3c7b70f6c 100644
--- a/src/librustc_metadata/dynamic_lib.rs
+++ b/src/librustc_metadata/dynamic_lib.rs
@@ -54,8 +54,16 @@ mod dl {
     use std::ffi::{CString, OsStr};
     use std::os::unix::prelude::*;
 
-    // `dlerror` is process global, so we can only allow a single thread at a
-    // time to call `dlsym` and `dlopen` if we want to check the error message.
+    // As of the 2017 revision of the POSIX standard (IEEE 1003.1-2017), it is
+    // implementation-defined whether `dlerror` is thread-safe (in which case it returns the most
+    // recent error in the calling thread) or not thread-safe (in which case it returns the most
+    // recent error in *any* thread).
+    //
+    // There's no easy way to tell what strategy is used by a given POSIX implementation, so we
+    // lock around all calls that can modify `dlerror` in this module lest we accidentally read an
+    // error from a different thread. This is bulletproof when we are the *only* code using the
+    // dynamic library APIs at a given point in time. However, it's still possible for us to race
+    // with other code (see #74469) on platforms where `dlerror` is not thread-safe.
     mod error {
         use std::ffi::CStr;
         use std::lazy::SyncLazy;
@@ -97,9 +105,9 @@ mod dl {
             return Ok(ret);
         }
 
-        // A NULL return from `dlopen` indicates that an error has
-        // definitely occurred, so if nothing is in `dlerror`, we are
-        // racing with another thread that has stolen our error message.
+        // A NULL return from `dlopen` indicates that an error has definitely occurred, so if
+        // nothing is in `dlerror`, we are racing with another thread that has stolen our error
+        // message. See the explanation on the `dl::error` module for more information.
         dlerror.get().and_then(|()| Err("Unknown error".to_string()))
     }
 
@@ -107,41 +115,26 @@ mod dl {
         handle: *mut u8,
         symbol: *const libc::c_char,
     ) -> Result<*mut u8, String> {
-        // HACK(#74469): On some platforms, users observed foreign code
-        // (specifically libc) invoking `dlopen`/`dlsym` in parallel with the
-        // functions in this module. This is problematic because, according to
-        // the POSIX API documentation, `dlerror` must be called to determine
-        // whether `dlsym` succeeded. Unlike `dlopen`, a NULL return value may
-        // indicate a successfully resolved symbol with an address of zero.
-        //
-        // Because symbols with address zero shouldn't occur in practice, we
-        // treat them as errors on platforms with misbehaving libc
-        // implementations.
-        const DLSYM_NULL_IS_ERROR: bool = cfg!(target_os = "illumos");
-
         let mut dlerror = error::lock();
 
-        // No need to flush `dlerror` if we aren't using it to determine whether
-        // the subsequent call to `dlsym` succeeded. If an error occurs, any
-        // stale value will be overwritten.
-        if !DLSYM_NULL_IS_ERROR {
-            dlerror.clear();
-        }
+        // Unlike `dlopen`, it's possible for `dlsym` to return NULL without overwriting `dlerror`.
+        // Because of this, we clear `dlerror` before calling `dlsym` to avoid picking up a stale
+        // error message by accident.
+        dlerror.clear();
 
         let ret = libc::dlsym(handle as *mut libc::c_void, symbol) as *mut u8;
 
-        // A non-NULL return value *always* indicates success. There's no need
-        // to check `dlerror`.
         if !ret.is_null() {
             return Ok(ret);
         }
 
-        match dlerror.get() {
-            Ok(()) if DLSYM_NULL_IS_ERROR => Err("Unknown error".to_string()),
-            Ok(()) => Ok(ret),
-
-            Err(msg) => Err(msg),
-        }
+        // If `dlsym` returns NULL but there is nothing in `dlerror` it means one of two things:
+        // - We tried to load a symbol mapped to address 0. This is not technically an error but is
+        //   unlikely to occur in practice and equally unlikely to be handled correctly by calling
+        //   code. Therefore we treat it as an error anyway.
+        // - An error has occurred, but we are racing with another thread that has stolen our error
+        //   message. See the explanation on the `dl::error` module for more information.
+        dlerror.get().and_then(|()| Err("Tried to load symbol mapped to address 0".to_string()))
     }
 
     pub(super) unsafe fn close(handle: *mut u8) {