[pushed] Block signals during the initial part of dlopen

Message ID 20191127203048.27C2028173@gnutoolchain-gerrit.osci.io
State Committed
Headers

Commit Message

Simon Marchi (Code Review) Nov. 27, 2019, 8:30 p.m. UTC
  Sourceware to Gerrit sync has submitted this change.

Change URL: https://gnutoolchain-gerrit.osci.io/r/c/glibc/+/472
......................................................................

Block signals during the initial part of dlopen

Lazy binding in a signal handler that interrupts a dlopen sees
intermediate dynamic linker state.  This has likely been always
unsafe, but with the new pending NODELETE state, this is clearly
incorrect.  Other threads are excluded via the loader lock, but the
current thread is not.  Blocking signals until right before ELF
constructors run is the safe thing to do.

Change-Id: Iad079080ebe7442c13313ba11dc2797953faef35
---
M elf/dl-open.c
1 file changed, 26 insertions(+), 2 deletions(-)

Approvals:
  Christian Brauner: Looks good to me, approved
  

Comments

Joseph Myers Nov. 28, 2019, 6:04 p.m. UTC | #1
On Wed, 27 Nov 2019, Sourceware to Gerrit sync (Code Review) wrote:

> Block signals during the initial part of dlopen
> 
> Lazy binding in a signal handler that interrupts a dlopen sees
> intermediate dynamic linker state.  This has likely been always
> unsafe, but with the new pending NODELETE state, this is clearly
> incorrect.  Other threads are excluded via the loader lock, but the
> current thread is not.  Blocking signals until right before ELF
> constructors run is the safe thing to do.

This change (commit a2e8aa0d9ea648068d8be52dd7b15f1b6a008e23) breaks the 
build for i686-gnu (unfortunately this was after a syntax error was 
introduced in build-many-glibcs.py so my bots quietly died on re-execing 
with the syntax error, and so didn't get to find this build failure).

There are a series of errors linking ld.so starting with:

/scratch/jmyers/glibc/many9/install/compilers/i686-gnu/lib/gcc/i686-glibc-gnu/9.2.1/../../../../i686-glibc-gnu/bin/ld: /scratch/jmyers/glibc/many9/build/glibcs/i686-gnu/glibc/libc_pic.a(libc_fatal.os): in function `__GI___libc_fatal':
/scratch/jmyers/glibc/many9/src/glibc/libio/../sysdeps/posix/libc_fatal.c:161: multiple definition of `__libc_fatal'; /scratch/jmyers/glibc/many9/build/glibcs/i686-gnu/glibc/elf/dl-allobjs.os:/scratch/jmyers/glibc/many9/src/glibc/elf/dl-minimal.c:188: first defined here

and leading up to:

make[3]: *** [/scratch/jmyers/glibc/many9/build/glibcs/i686-gnu/glibc/elf/librtld.map] Error 1
  

Patch

diff --git a/elf/dl-open.c b/elf/dl-open.c
index 7415c09..1051e22 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -34,6 +34,7 @@ 
 #include <atomic.h>
 #include <libc-internal.h>
 #include <array_length.h>
+#include <internal-signals.h>
 
 #include <dl-dst.h>
 #include <dl-prop.h>
@@ -52,6 +53,10 @@ 
   /* Namespace ID.  */
   Lmid_t nsid;
 
+  /* Original signal mask.  Used for unblocking signal handlers before
+     running ELF constructors.  */
+  sigset_t original_signal_mask;
+
   /* Original value of _ns_global_scope_pending_adds.  Set by
      dl_open_worker.  Only valid if nsid is a real namespace
      (non-negative).  */
@@ -524,12 +529,16 @@ 
   if (new == NULL)
     {
       assert (mode & RTLD_NOLOAD);
+      __libc_signal_restore_set (&args->original_signal_mask);
       return;
     }
 
   if (__glibc_unlikely (mode & __RTLD_SPROF))
-    /* This happens only if we load a DSO for 'sprof'.  */
-    return;
+    {
+      /* This happens only if we load a DSO for 'sprof'.  */
+      __libc_signal_restore_set (&args->original_signal_mask);
+      return;
+    }
 
   /* This object is directly loaded.  */
   ++new->l_direct_opencount;
@@ -565,6 +574,7 @@ 
 
       assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
 
+      __libc_signal_restore_set (&args->original_signal_mask);
       return;
     }
 
@@ -748,6 +758,10 @@ 
   if (mode & RTLD_GLOBAL)
     add_to_global_resize (new);
 
+  /* Unblock signals.  Data structures are now consistent, and
+     application code may run.  */
+  __libc_signal_restore_set (&args->original_signal_mask);
+
   /* Run the initializer functions of new objects.  Temporarily
      disable the exception handler, so that lazy binding failures are
      fatal.  */
@@ -837,6 +851,10 @@ 
   args.argv = argv;
   args.env = env;
 
+  /* Recursive lazy binding during manipulation of the dynamic loader
+     structures may result in incorrect behavior.  */
+  __libc_signal_block_all (&args.original_signal_mask);
+
   struct dl_exception exception;
   int errcode = _dl_catch_exception (&exception, dl_open_worker, &args);
 
@@ -877,10 +895,16 @@ 
 
 	  _dl_close_worker (args.map, true);
 
+	  /* Restore the signal mask.  In the success case, this
+	     happens inside dl_open_worker.  */
+	  __libc_signal_restore_set (&args.original_signal_mask);
+
 	  /* All link_map_nodelete_pending objects should have been
 	     deleted at this point, which is why it is not necessary
 	     to reset the flag here.  */
 	}
+      else
+	__libc_signal_restore_set (&args.original_signal_mask);
 
       assert (_dl_debug_initialize (0, args.nsid)->r_state == RT_CONSISTENT);