548bcb
commit 1387ad6225c2222f027790e3f460e31aa5dd2c54
548bcb
Author: Szabolcs Nagy <szabolcs.nagy@arm.com>
548bcb
Date:   Wed Dec 30 19:19:37 2020 +0000
548bcb
548bcb
    elf: Fix data races in pthread_create and TLS access [BZ #19329]
548bcb
    
548bcb
    DTV setup at thread creation (_dl_allocate_tls_init) is changed
548bcb
    to take the dlopen lock, GL(dl_load_lock).  Avoiding data races
548bcb
    here without locks would require design changes: the map that is
548bcb
    accessed for static TLS initialization here may be concurrently
548bcb
    freed by dlclose.  That use after free may be solved by only
548bcb
    locking around static TLS setup or by ensuring dlclose does not
548bcb
    free modules with static TLS, however currently every link map
548bcb
    with TLS has to be accessed at least to see if it needs static
548bcb
    TLS.  And even if that's solved, still a lot of atomics would be
548bcb
    needed to synchronize DTV related globals without a lock. So fix
548bcb
    both bug 19329 and bug 27111 with a lock that prevents DTV setup
548bcb
    running concurrently with dlopen or dlclose.
548bcb
    
548bcb
    _dl_update_slotinfo at TLS access still does not use any locks
548bcb
    so CONCURRENCY NOTES are added to explain the synchronization.
548bcb
    The early exit from the slotinfo walk when max_modid is reached
548bcb
    is not strictly necessary, but does not hurt either.
548bcb
    
548bcb
    An incorrect acquire load was removed from _dl_resize_dtv: it
548bcb
    did not synchronize with any release store or fence and
548bcb
    synchronization is now handled separately at thread creation
548bcb
    and TLS access time.
548bcb
    
548bcb
    There are still a number of racy read accesses to globals that
548bcb
    will be changed to relaxed MO atomics in a followup patch. This
548bcb
    should not introduce regressions compared to existing behaviour
548bcb
    and avoid cluttering the main part of the fix.
548bcb
    
548bcb
    Not all TLS access related data races got fixed here: there are
548bcb
    additional races at lazy tlsdesc relocations see bug 27137.
548bcb
    
548bcb
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
548bcb
548bcb
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
548bcb
index 15ed01d795a8627a..da83cd6ae2ee6504 100644
548bcb
--- a/elf/dl-tls.c
548bcb
+++ b/elf/dl-tls.c
548bcb
@@ -471,14 +471,11 @@ extern dtv_t _dl_static_dtv[];
548bcb
 #endif
548bcb
 
548bcb
 static dtv_t *
548bcb
-_dl_resize_dtv (dtv_t *dtv)
548bcb
+_dl_resize_dtv (dtv_t *dtv, size_t max_modid)
548bcb
 {
548bcb
   /* Resize the dtv.  */
548bcb
   dtv_t *newp;
548bcb
-  /* Load GL(dl_tls_max_dtv_idx) atomically since it may be written to by
548bcb
-     other threads concurrently.  */
548bcb
-  size_t newsize
548bcb
-    = atomic_load_acquire (&GL(dl_tls_max_dtv_idx)) + DTV_SURPLUS;
548bcb
+  size_t newsize = max_modid + DTV_SURPLUS;
548bcb
   size_t oldsize = dtv[-1].counter;
548bcb
 
548bcb
   if (dtv == GL(dl_initial_dtv))
548bcb
@@ -524,11 +521,14 @@ _dl_allocate_tls_init (void *result)
548bcb
   size_t total = 0;
548bcb
   size_t maxgen = 0;
548bcb
 
548bcb
+  /* Protects global dynamic TLS related state.  */
548bcb
+  __rtld_lock_lock_recursive (GL(dl_load_lock));
548bcb
+
548bcb
   /* Check if the current dtv is big enough.   */
548bcb
   if (dtv[-1].counter < GL(dl_tls_max_dtv_idx))
548bcb
     {
548bcb
       /* Resize the dtv.  */
548bcb
-      dtv = _dl_resize_dtv (dtv);
548bcb
+      dtv = _dl_resize_dtv (dtv, GL(dl_tls_max_dtv_idx));
548bcb
 
548bcb
       /* Install this new dtv in the thread data structures.  */
548bcb
       INSTALL_DTV (result, &dtv[-1]);
548bcb
@@ -596,6 +596,7 @@ _dl_allocate_tls_init (void *result)
548bcb
       listp = listp->next;
548bcb
       assert (listp != NULL);
548bcb
     }
548bcb
+  __rtld_lock_unlock_recursive (GL(dl_load_lock));
548bcb
 
548bcb
   /* The DTV version is up-to-date now.  */
548bcb
   dtv[0].counter = maxgen;
548bcb
@@ -730,12 +731,29 @@ _dl_update_slotinfo (unsigned long int req_modid)
548bcb
 
548bcb
   if (dtv[0].counter < listp->slotinfo[idx].gen)
548bcb
     {
548bcb
-      /* The generation counter for the slot is higher than what the
548bcb
-	 current dtv implements.  We have to update the whole dtv but
548bcb
-	 only those entries with a generation counter <= the one for
548bcb
-	 the entry we need.  */
548bcb
+      /* CONCURRENCY NOTES:
548bcb
+
548bcb
+	 Here the dtv needs to be updated to new_gen generation count.
548bcb
+
548bcb
+	 This code may be called during TLS access when GL(dl_load_lock)
548bcb
+	 is not held.  In that case the user code has to synchronize with
548bcb
+	 dlopen and dlclose calls of relevant modules.  A module m is
548bcb
+	 relevant if the generation of m <= new_gen and dlclose of m is
548bcb
+	 synchronized: a memory access here happens after the dlopen and
548bcb
+	 before the dlclose of relevant modules.  The dtv entries for
548bcb
+	 relevant modules need to be updated, other entries can be
548bcb
+	 arbitrary.
548bcb
+
548bcb
+	 This e.g. means that the first part of the slotinfo list can be
548bcb
+	 accessed race free, but the tail may be concurrently extended.
548bcb
+	 Similarly relevant slotinfo entries can be read race free, but
548bcb
+	 other entries are racy.  However updating a non-relevant dtv
548bcb
+	 entry does not affect correctness.  For a relevant module m,
548bcb
+	 max_modid >= modid of m.  */
548bcb
       size_t new_gen = listp->slotinfo[idx].gen;
548bcb
       size_t total = 0;
548bcb
+      size_t max_modid  = atomic_load_relaxed (&GL(dl_tls_max_dtv_idx));
548bcb
+      assert (max_modid >= req_modid);
548bcb
 
548bcb
       /* We have to look through the entire dtv slotinfo list.  */
548bcb
       listp =  GL(dl_tls_dtv_slotinfo_list);
548bcb
@@ -745,12 +763,14 @@ _dl_update_slotinfo (unsigned long int req_modid)
548bcb
 	    {
548bcb
 	      size_t modid = total + cnt;
548bcb
 
548bcb
+	      /* Later entries are not relevant.  */
548bcb
+	      if (modid > max_modid)
548bcb
+		break;
548bcb
+
548bcb
 	      size_t gen = listp->slotinfo[cnt].gen;
548bcb
 
548bcb
 	      if (gen > new_gen)
548bcb
-		/* This is a slot for a generation younger than the
548bcb
-		   one we are handling now.  It might be incompletely
548bcb
-		   set up so ignore it.  */
548bcb
+		/* Not relevant.  */
548bcb
 		continue;
548bcb
 
548bcb
 	      /* If the entry is older than the current dtv layout we
548bcb
@@ -767,7 +787,7 @@ _dl_update_slotinfo (unsigned long int req_modid)
548bcb
 		    continue;
548bcb
 
548bcb
 		  /* Resize the dtv.  */
548bcb
-		  dtv = _dl_resize_dtv (dtv);
548bcb
+		  dtv = _dl_resize_dtv (dtv, max_modid);
548bcb
 
548bcb
 		  assert (modid <= dtv[-1].counter);
548bcb
 
548bcb
@@ -789,8 +809,17 @@ _dl_update_slotinfo (unsigned long int req_modid)
548bcb
 	    }
548bcb
 
548bcb
 	  total += listp->len;
548bcb
+	  if (total > max_modid)
548bcb
+	    break;
548bcb
+
548bcb
+	  /* Synchronize with _dl_add_to_slotinfo.  Ideally this would
548bcb
+	     be consume MO since we only need to order the accesses to
548bcb
+	     the next node after the read of the address and on most
548bcb
+	     hardware (other than alpha) a normal load would do that
548bcb
+	     because of the address dependency.  */
548bcb
+	  listp = atomic_load_acquire (&listp->next);
548bcb
 	}
548bcb
-      while ((listp = listp->next) != NULL);
548bcb
+      while (listp != NULL);
548bcb
 
548bcb
       /* This will be the new maximum generation counter.  */
548bcb
       dtv[0].counter = new_gen;
548bcb
@@ -982,7 +1011,7 @@ _dl_add_to_slotinfo (struct link_map *l, bool do_add)
548bcb
 	 the first slot.  */
548bcb
       assert (idx == 0);
548bcb
 
548bcb
-      listp = prevp->next = (struct dtv_slotinfo_list *)
548bcb
+      listp = (struct dtv_slotinfo_list *)
548bcb
 	malloc (sizeof (struct dtv_slotinfo_list)
548bcb
 		+ TLS_SLOTINFO_SURPLUS * sizeof (struct dtv_slotinfo));
548bcb
       if (listp == NULL)
548bcb
@@ -996,6 +1025,8 @@ cannot create TLS data structures"));
548bcb
       listp->next = NULL;
548bcb
       memset (listp->slotinfo, '\0',
548bcb
 	      TLS_SLOTINFO_SURPLUS * sizeof (struct dtv_slotinfo));
548bcb
+      /* Synchronize with _dl_update_slotinfo.  */
548bcb
+      atomic_store_release (&prevp->next, listp);
548bcb
     }
548bcb
 
548bcb
   /* Add the information into the slotinfo data structure.  */