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