stdlib: Fix data race in __run_exit_handlers

Message ID 20200920233756.3886676-1-vitalybuka@google.com
State Superseded
Headers
Series stdlib: Fix data race in __run_exit_handlers |

Commit Message

Vitaly Buka Sept. 20, 2020, 11:37 p.m. UTC
  Read f->func.cxa under the lock.

There is a clear data race:
thread 0: __run_exit_handlers unlock __exit_funcs_lock
thread 1: __internal_atexit locks __exit_funcs_lock
thread 0: f->flavor = ef_free;
thread 1: sees ef_free and use it as new
thread 1: new->func.cxa.fn = (void (*) (void *, int)) func;
thread 1: new->func.cxa.arg = arg;
thread 1: new->flavor = ef_cxa;
thread 0: cxafct = f->func.cxa.fn;  // it's wrong fn!
thread 0: cxafct (f->func.cxa.arg, status);  // it's wrong arg!
thread 0: goto restart;
thread 0: call the same exit_function again as it's ef_cxa

It's incredibly rare so I can't create reasonable test.
---
 stdlib/exit.c | 20 ++++++++++++++------
 1 file changed, 14 insertions(+), 6 deletions(-)
  

Patch

diff --git a/stdlib/exit.c b/stdlib/exit.c
index 7bca1cdc14..9c79aa4a62 100644
--- a/stdlib/exit.c
+++ b/stdlib/exit.c
@@ -72,44 +72,52 @@  __run_exit_handlers (int status, struct exit_function_list **listp,
 	  struct exit_function *const f = &cur->fns[--cur->idx];
 	  const uint64_t new_exitfn_called = __new_exitfn_called;
 
-	  /* Unlock the list while we call a foreign function.  */
-	  __libc_lock_unlock (__exit_funcs_lock);
 	  switch (f->flavor)
 	    {
 	      void (*atfct) (void);
 	      void (*onfct) (int status, void *arg);
 	      void (*cxafct) (void *arg, int status);
+	      void *arg;
 
 	    case ef_free:
 	    case ef_us:
 	      break;
 	    case ef_on:
 	      onfct = f->func.on.fn;
+              arg = f->func.on.arg;
 #ifdef PTR_DEMANGLE
 	      PTR_DEMANGLE (onfct);
 #endif
-	      onfct (status, f->func.on.arg);
+	      /* Unlock the list while we call a foreign function.  */
+	      __libc_lock_unlock (__exit_funcs_lock);
+	      onfct (status, arg);
+	      __libc_lock_lock (__exit_funcs_lock);
 	      break;
 	    case ef_at:
 	      atfct = f->func.at;
 #ifdef PTR_DEMANGLE
 	      PTR_DEMANGLE (atfct);
 #endif
+	      /* Unlock the list while we call a foreign function.  */
+	      __libc_lock_unlock (__exit_funcs_lock);
 	      atfct ();
+	      __libc_lock_lock (__exit_funcs_lock);
 	      break;
 	    case ef_cxa:
 	      /* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
 		 we must mark this function as ef_free.  */
 	      f->flavor = ef_free;
 	      cxafct = f->func.cxa.fn;
+	      arg = f->func.cxa.arg;
 #ifdef PTR_DEMANGLE
 	      PTR_DEMANGLE (cxafct);
 #endif
-	      cxafct (f->func.cxa.arg, status);
+	      /* Unlock the list while we call a foreign function.  */
+	      __libc_lock_unlock (__exit_funcs_lock);
+	      cxafct (arg, status);
+	      __libc_lock_lock (__exit_funcs_lock);
 	      break;
 	    }
-	  /* Re-lock again before looking at global state.  */
-	  __libc_lock_lock (__exit_funcs_lock);
 
 	  if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
 	    /* The last exit function, or another thread, has registered