about summary refs log tree commit diff
path: root/elf
diff options
context:
space:
mode:
Diffstat (limited to 'elf')
-rw-r--r--elf/dl-misc.c18
-rw-r--r--elf/dl-reloc.c26
-rw-r--r--elf/dl-support.c3
-rw-r--r--elf/dl-tls.c139
-rw-r--r--elf/rtld.c24
5 files changed, 180 insertions, 30 deletions
diff --git a/elf/dl-misc.c b/elf/dl-misc.c
index b3c56ab6b0..ec70298d42 100644
--- a/elf/dl-misc.c
+++ b/elf/dl-misc.c
@@ -530,12 +530,21 @@ __signal_safe_memalign (size_t boundary, size_t size)
 void * weak_function
 __signal_safe_malloc (size_t size)
 {
+  if (!GLRO(dl_async_signal_safe))
+    return malloc (size);
+
   return __signal_safe_memalign (1, size);
 }
 
 void weak_function
 __signal_safe_free (void *ptr)
 {
+  if (!GLRO(dl_async_signal_safe))
+    {
+      free (ptr);
+      return;
+    }
+
   if (ptr == NULL)
     return;
 
@@ -549,6 +558,9 @@ __signal_safe_free (void *ptr)
 void * weak_function
 __signal_safe_realloc (void *ptr, size_t size)
 {
+  if (!GLRO(dl_async_signal_safe))
+    return realloc (ptr, size);
+
   if (size == 0)
     {
       __signal_safe_free (ptr);
@@ -567,7 +579,8 @@ __signal_safe_realloc (void *ptr, size_t size)
   if (new_ptr == NULL)
     return NULL;
 
-  memcpy (new_ptr, ptr, old_size);
+  /* Copy over the old block (but not its header).  */
+  memcpy (new_ptr, ptr, old_size - sizeof (*header));
   __signal_safe_free (ptr);
 
   return new_ptr;
@@ -576,6 +589,9 @@ __signal_safe_realloc (void *ptr, size_t size)
 void * weak_function
 __signal_safe_calloc (size_t nmemb, size_t size)
 {
+  if (!GLRO(dl_async_signal_safe))
+    return calloc (nmemb, size);
+
   void *ptr = __signal_safe_malloc (nmemb * size);
   if (ptr == NULL)
     return NULL;
diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c
index 8696453566..1625f35d1b 100644
--- a/elf/dl-reloc.c
+++ b/elf/dl-reloc.c
@@ -72,6 +72,9 @@ _dl_try_allocate_static_tls (struct link_map *map)
 
   size_t offset = GL(dl_tls_static_used) + (freebytes - n * map->l_tls_align
 					    - map->l_tls_firstbyte_offset);
+
+  if (!GLRO(dl_async_signal_safe))
+    map->l_tls_offset = GL(dl_tls_static_used) = offset;
 #elif TLS_DTV_AT_TP
   /* dl_tls_static_used includes the TCB at the beginning.  */
   size_t offset = (ALIGN_UP(GL(dl_tls_static_used)
@@ -83,9 +86,15 @@ _dl_try_allocate_static_tls (struct link_map *map)
   if (used > GL(dl_tls_static_size))
     goto fail;
 
+  if (!GLRO(dl_async_signal_safe)) {
+    map->l_tls_offset = offset;
+    map->l_tls_firstbyte_offset = GL(dl_tls_static_used);
+    GL(dl_tls_static_used) = used;
+  }
 #else
 # error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
 #endif
+  if (GLRO(dl_async_signal_safe)) {
   /* We've computed the new value we want, now try to install it.  */
   ptrdiff_t val;
   if ((val = map->l_tls_offset) == NO_TLS_OFFSET)
@@ -110,14 +119,15 @@ _dl_try_allocate_static_tls (struct link_map *map)
       goto fail;
     }
   /* We installed the value; now update the globals.  */
-#if TLS_TCB_AT_TP
+#if TLS_TCB_AT_TP // second
   GL(dl_tls_static_used) = offset;
-#elif TLS_DTV_AT_TP
+#elif TLS_DTV_AT_TP // second
   map->l_tls_firstbyte_offset = GL(dl_tls_static_used);
   GL(dl_tls_static_used) = used;
-#else
-# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
-#endif
+#else // second
+# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"  // second
+#endif // second
+  }
 
   /* If the object is not yet relocated we cannot initialize the
      static TLS region.  Delay it.  */
@@ -147,12 +157,14 @@ _dl_allocate_static_tls (struct link_map *map)
      (including this one) and update this map's TLS entry. A signal handler
      accessing TLS would try to do the same update and break.  */
   sigset_t old;
-  _dl_mask_all_signals (&old);
+  if (GLRO(dl_async_signal_safe))
+    _dl_mask_all_signals (&old);
   int err = -1;
   if (map->l_tls_offset != FORCED_DYNAMIC_TLS_OFFSET)
     err = _dl_try_allocate_static_tls (map);
 
-  _dl_unmask_signals (&old);
+  if (GLRO(dl_async_signal_safe))
+    _dl_unmask_signals (&old);
   if (err != 0)
     {
       _dl_signal_error (0, map->l_name, NULL, N_("\
diff --git a/elf/dl-support.c b/elf/dl-support.c
index febc5e2f70..9349134fa0 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -73,6 +73,9 @@ const char *_google_exec_origin_dir;
 /* Nonzero if runtime lookup should not update the .got/.plt.  */
 int _dl_bind_not;
 
+/* Nonzero if TLS handling should be async-signal-safe.  */
+int _dl_async_signal_safe;
+
 /* A dummy link map for the executable, used by dlopen to access the global
    scope.  We don't export any symbols ourselves, so this can be minimal.  */
 static struct link_map _dl_main_map =
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
index 5659adbba5..47242097fc 100644
--- a/elf/dl-tls.c
+++ b/elf/dl-tls.c
@@ -409,14 +409,14 @@ _dl_resize_dtv (dtv_t *dtv)
 	 dl-minimal.c malloc instead of the real malloc.  We can't free
 	 it, we have to abandon the old storage.  */
 
-      newp = malloc ((2 + newsize) * sizeof (dtv_t));
+      newp = __signal_safe_malloc ((2 + newsize) * sizeof (dtv_t));
       if (newp == NULL)
 	oom ();
       memcpy (newp, &dtv[-1], (2 + oldsize) * sizeof (dtv_t));
     }
   else
     {
-      newp = realloc (&dtv[-1],
+      newp = __signal_safe_realloc (&dtv[-1],
 		      (2 + newsize) * sizeof (dtv_t));
       if (newp == NULL)
 	oom ();
@@ -534,13 +534,15 @@ _dl_allocate_tls (void *mem)
 }
 rtld_hidden_def (_dl_allocate_tls)
 
+/* Clear the given dtv.  (We have this here because __signal_safe_free is
+   not visible to nptl/allocatestack.c.)  */
+
 void
 _dl_clear_dtv (dtv_t *dtv)
 {
   for (size_t cnt = 0; cnt < dtv[-1].counter; ++cnt)
-    if (/*! dtv[1 + cnt].pointer.is_static */ 1
-	&& dtv[1 + cnt].pointer.val != TLS_DTV_UNALLOCATED)
-      __signal_safe_free (dtv[1 + cnt].pointer.val);
+    __signal_safe_free (dtv[1 + cnt].pointer.to_free);
+
   memset (dtv, '\0', (dtv[-1].counter + 1) * sizeof (dtv_t));
 }
 
@@ -584,8 +586,53 @@ rtld_hidden_def (_dl_deallocate_tls)
 #  define GET_ADDR_OFFSET ti->ti_offset
 # endif
 
+/* Allocate one DTV entry.  */
+static struct dtv_pointer
+allocate_dtv_entry (size_t alignment, size_t size)
+{
+  if (powerof2 (alignment) && alignment <= _Alignof (max_align_t))
+    {
+      /* The alignment is supported by malloc.  */
+      void *ptr = malloc (size);
+      return (struct dtv_pointer) { ptr, ptr };
+    }
+
+  /* Emulate memalign to by manually aligning a pointer returned by
+     malloc.  First compute the size with an overflow check.  */
+  size_t alloc_size = size + alignment;
+  if (alloc_size < size)
+    return (struct dtv_pointer) {};
+
+  /* Perform the allocation.  This is the pointer we need to free
+     later.  */
+  void *start = malloc (alloc_size);
+  if (start == NULL)
+    return (struct dtv_pointer) {};
+
+  /* Find the aligned position within the larger allocation.  */
+  void *aligned = (void *) roundup ((uintptr_t) start, alignment);
+
+  return (struct dtv_pointer) { .val = aligned, .to_free = start };
+}
+
+static struct dtv_pointer
+allocate_and_init (struct link_map *map)
+{
+  struct dtv_pointer result = allocate_dtv_entry
+    (map->l_tls_align, map->l_tls_blocksize);
+  if (result.val == NULL)
+    oom ();
+
+  /* Initialize the memory.  */
+  memset (__mempcpy (result.val, map->l_tls_initimage,
+		     map->l_tls_initimage_size),
+	  '\0', map->l_tls_blocksize - map->l_tls_initimage_size);
+
+  return result;
+}
+
 static void
-allocate_and_init (dtv_t *dtv, struct link_map *map)
+signal_safe_allocate_and_init (dtv_t *dtv, struct link_map *map)
 {
   void *newp;
   newp = __signal_safe_memalign (map->l_tls_align, map->l_tls_blocksize);
@@ -642,13 +689,18 @@ _dl_update_slotinfo (unsigned long int req_modid)
       size_t total = 0;
       sigset_t old;
 
-      _dl_mask_all_signals (&old);
-      /* We use the signal mask as a lock against reentrancy here.
-         Check that a signal taken before the lock didn't already
-         update us.  */
-      dtv = THREAD_DTV ();
-      if (dtv[0].counter >= listp->slotinfo[idx].gen)
-        goto out;
+      if (GLRO(dl_async_signal_safe)) {
+	_dl_mask_all_signals (&old);
+	/* We use the signal mask as a lock against reentrancy here.
+	   Check that a signal taken before the lock didn't already
+	   update us.  */
+	dtv = THREAD_DTV ();
+	if (dtv[0].counter >= listp->slotinfo[idx].gen)
+	  {
+	    _dl_unmask_signals (&old);
+	    return the_map;
+	  }
+      }
       /* We have to look through the entire dtv slotinfo list.  */
       listp =  GL(dl_tls_dtv_slotinfo_list);
       do
@@ -674,13 +726,13 @@ _dl_update_slotinfo (unsigned long int req_modid)
 	      struct link_map *map = listp->slotinfo[cnt].map;
 	      if (map == NULL)
 		{
-		  if (dtv[-1].counter >= total + cnt)
+		  if (dtv[-1].counter >= modid)
 		    {
 		      /* If this modid was used at some point the memory
 			 might still be allocated.  */
-		      __signal_safe_free (dtv[total + cnt].pointer.to_free);
-		      dtv[total + cnt].pointer.val = TLS_DTV_UNALLOCATED;
-		      dtv[total + cnt].pointer.to_free = NULL;
+		      __signal_safe_free (dtv[modid].pointer.to_free);
+		      dtv[modid].pointer.val = TLS_DTV_UNALLOCATED;
+		      dtv[modid].pointer.to_free = NULL;
 		    }
 
 		  continue;
@@ -718,8 +770,8 @@ _dl_update_slotinfo (unsigned long int req_modid)
 
       /* This will be the new maximum generation counter.  */
       dtv[0].counter = new_gen;
-   out:
-      _dl_unmask_signals (&old);
+      if (GLRO(dl_async_signal_safe))
+	_dl_unmask_signals (&old);
     }
 
   return the_map;
@@ -745,6 +797,50 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
 
       the_map = listp->slotinfo[idx].map;
     }
+
+  if (!GLRO(dl_async_signal_safe)) {
+
+  /* Make sure that, if a dlopen running in parallel forces the
+     variable into static storage, we'll wait until the address in the
+     static TLS block is set up, and use that.  If we're undecided
+     yet, make sure we make the decision holding the lock as well.  */
+  if (__glibc_unlikely (the_map->l_tls_offset
+			!= FORCED_DYNAMIC_TLS_OFFSET))
+    {
+      __rtld_lock_lock_recursive (GL(dl_load_lock));
+      if (__glibc_likely (the_map->l_tls_offset == NO_TLS_OFFSET))
+	{
+	  the_map->l_tls_offset = FORCED_DYNAMIC_TLS_OFFSET;
+	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
+	}
+      else if (__glibc_likely (the_map->l_tls_offset
+			       != FORCED_DYNAMIC_TLS_OFFSET))
+	{
+#if TLS_TCB_AT_TP
+	  void *p = (char *) THREAD_SELF - the_map->l_tls_offset;
+#elif TLS_DTV_AT_TP
+	  void *p = (char *) THREAD_SELF + the_map->l_tls_offset + TLS_PRE_TCB_SIZE;
+#else
+# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
+#endif
+	  __rtld_lock_unlock_recursive (GL(dl_load_lock));
+
+	  dtv[GET_ADDR_MODULE].pointer.to_free = NULL;
+	  dtv[GET_ADDR_MODULE].pointer.val = p;
+
+	  return (char *) p + GET_ADDR_OFFSET;
+	}
+      else
+	__rtld_lock_unlock_recursive (GL(dl_load_lock));
+    }
+  struct dtv_pointer result = allocate_and_init (the_map);
+  dtv[GET_ADDR_MODULE].pointer = result;
+  assert (result.to_free != NULL);
+
+  return (char *) result.val + GET_ADDR_OFFSET;
+
+  } else {
+
   sigset_t old;
   _dl_mask_all_signals (&old);
 
@@ -778,11 +874,11 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
 
   if (offset == FORCED_DYNAMIC_TLS_OFFSET)
     {
-      allocate_and_init (&dtv[GET_ADDR_MODULE], the_map);
+      signal_safe_allocate_and_init (&dtv[GET_ADDR_MODULE], the_map);
     }
   else
     {
-      void **pp = &dtv[GET_ADDR_MODULE].pointer.val;
+      void ** volatile pp = &dtv[GET_ADDR_MODULE].pointer.val;
       while (atomic_forced_read (*pp) == TLS_DTV_UNALLOCATED)
 	{
 	  /* for lack of a better (safe) thing to do, just spin.
@@ -803,6 +899,7 @@ tls_get_addr_tail (GET_ADDR_ARGS, dtv_t *dtv, struct link_map *the_map)
   _dl_unmask_signals (&old);
 
   return (char *) dtv[GET_ADDR_MODULE].pointer.val + GET_ADDR_OFFSET;
+  }
 }
 
 
diff --git a/elf/rtld.c b/elf/rtld.c
index 9250cf4f94..f4e382d9c7 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2502,6 +2502,9 @@ process_envvars (enum mode *modep)
   enum mode mode = normal;
   char *debug_output = NULL;
 
+  /* Enable async-signal-safe TLS by default.  */
+  GLRO(dl_async_signal_safe) = 1;
+
   /* This is the default place for profiling data file.  */
   GLRO(dl_profile_output)
     = &"/var/tmp\0/var/profile"[__libc_enable_secure ? 9 : 0];
@@ -2525,6 +2528,10 @@ process_envvars (enum mode *modep)
 	  /* Warning level, verbose or not.  */
 	  if (memcmp (envline, "WARN", 4) == 0)
 	    GLRO(dl_verbose) = envline[5] != '\0';
+#if 0 /* enable to get runtime control over async signal safety */
+	  if (memcmp (envline, "SAFE", 4) == 0)
+	    GLRO(dl_async_signal_safe) = 1;
+#endif
 	  break;
 
 	case 5:
@@ -2537,7 +2544,12 @@ process_envvars (enum mode *modep)
 	  if (memcmp (envline, "AUDIT", 5) == 0)
 	    audit_list_string = &envline[6];
 	  break;
-
+#if 0 /* enable to get runtime control over async signal safety */
+	case 6:
+	  if (memcmp (envline, "UNSAFE", 6) == 0)
+	    GLRO(dl_async_signal_safe) = 0;
+	  break;
+#endif
 	case 7:
 	  /* Print information about versions.  */
 	  if (memcmp (envline, "VERBOSE", 7) == 0)
@@ -2666,6 +2678,16 @@ process_envvars (enum mode *modep)
   /* The caller wants this information.  */
   *modep = mode;
 
+#if 0 /* enable this to help debug async-safe TLS */
+  if (GLRO(dl_debug_mask))
+    {
+      if (GLRO(dl_async_signal_safe))
+	_dl_printf ("TLS is async-signal-safe\n");
+      else
+	_dl_printf ("TLS is NOT async-signal-safe\n");
+    }
+#endif /* for async-safe TLS */
+
   /* Extra security for SUID binaries.  Remove all dangerous environment
      variables.  */
   if (__builtin_expect (__libc_enable_secure, 0))