5f7b84
commit 440b7f8653e4ed8f6e1425145208050b795e9a6c
5f7b84
Author: Florian Weimer <fweimer@redhat.com>
5f7b84
Date:   Thu Oct 31 18:25:39 2019 +0100
5f7b84
5f7b84
    Avoid late failure in dlopen in global scope update [BZ #25112]
5f7b84
    
5f7b84
    The call to add_to_global in dl_open_worker happens after running ELF
5f7b84
    constructors for new objects.  At this point, proper recovery from
5f7b84
    malloc failure would be quite complicated: We would have to run the
5f7b84
    ELF destructors and close all opened objects, something that we
5f7b84
    currently do not do.
5f7b84
    
5f7b84
    Instead, this change splits add_to_global into two phases,
5f7b84
    add_to_global_resize (which can raise an exception, called before ELF
5f7b84
    constructors run), and add_to_global_update (which cannot, called
5f7b84
    after ELF constructors).  A complication arises due to recursive
5f7b84
    dlopen: After the inner dlopen consumes some space, the pre-allocation
5f7b84
    in the outer dlopen may no longer be sufficient.  A new member in the
5f7b84
    namespace structure, _ns_global_scope_pending_adds keeps track of the
5f7b84
    maximum number of objects that need to be added to the global scope.
5f7b84
    This enables the inner add_to_global_resize call to take into account
5f7b84
    the needs of an outer dlopen.
5f7b84
    
5f7b84
    Most code in the dynamic linker assumes that the number of global
5f7b84
    scope entries fits into an unsigned int (matching the r_nlist member
5f7b84
    of struct r_scop_elem).  Therefore, change the type of
5f7b84
    _ns_global_scope_alloc to unsigned int (from size_t), and add overflow
5f7b84
    checks.
5f7b84
    
5f7b84
    Change-Id: Ie08e2f318510d5a6a4bcb1c315f46791b5b77524
5f7b84
5f7b84
diff --git a/elf/dl-open.c b/elf/dl-open.c
5f7b84
index c9c0254ee74c4f4b..85db4f0ecb5f29ce 100644
5f7b84
--- a/elf/dl-open.c
5f7b84
+++ b/elf/dl-open.c
5f7b84
@@ -50,22 +50,38 @@ struct dl_open_args
5f7b84
   struct link_map *map;
5f7b84
   /* Namespace ID.  */
5f7b84
   Lmid_t nsid;
5f7b84
+
5f7b84
+  /* Original value of _ns_global_scope_pending_adds.  Set by
5f7b84
+     dl_open_worker.  Only valid if nsid is a real namespace
5f7b84
+     (non-negative).  */
5f7b84
+  unsigned int original_global_scope_pending_adds;
5f7b84
+
5f7b84
   /* Original parameters to the program and the current environment.  */
5f7b84
   int argc;
5f7b84
   char **argv;
5f7b84
   char **env;
5f7b84
 };
5f7b84
 
5f7b84
+/* Called in case the global scope cannot be extended.  */
5f7b84
+static void __attribute__ ((noreturn))
5f7b84
+add_to_global_resize_failure (struct link_map *new)
5f7b84
+{
5f7b84
+  _dl_signal_error (ENOMEM, new->l_libname->name, NULL,
5f7b84
+		    N_ ("cannot extend global scope"));
5f7b84
+}
5f7b84
 
5f7b84
-static int
5f7b84
-add_to_global (struct link_map *new)
5f7b84
+/* Grow the global scope array for the namespace, so that all the new
5f7b84
+   global objects can be added later in add_to_global_update, without
5f7b84
+   risk of memory allocation failure.  add_to_global_resize raises
5f7b84
+   exceptions for memory allocation errors.  */
5f7b84
+static void
5f7b84
+add_to_global_resize (struct link_map *new)
5f7b84
 {
5f7b84
-  struct link_map **new_global;
5f7b84
-  unsigned int to_add = 0;
5f7b84
-  unsigned int cnt;
5f7b84
+  struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
5f7b84
 
5f7b84
   /* Count the objects we have to put in the global scope.  */
5f7b84
-  for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
5f7b84
+  unsigned int to_add = 0;
5f7b84
+  for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
5f7b84
     if (new->l_searchlist.r_list[cnt]->l_global == 0)
5f7b84
       ++to_add;
5f7b84
 
5f7b84
@@ -83,47 +99,51 @@ add_to_global (struct link_map *new)
5f7b84
      in an realloc() call.  Therefore we allocate a completely new
5f7b84
      array the first time we have to add something to the locale scope.  */
5f7b84
 
5f7b84
-  struct link_namespaces *ns = &GL(dl_ns)[new->l_ns];
5f7b84
+  if (__builtin_add_overflow (ns->_ns_global_scope_pending_adds, to_add,
5f7b84
+			      &ns->_ns_global_scope_pending_adds))
5f7b84
+    add_to_global_resize_failure (new);
5f7b84
+
5f7b84
+  unsigned int new_size = 0; /* 0 means no new allocation.  */
5f7b84
+  void *old_global = NULL; /* Old allocation if free-able.  */
5f7b84
+
5f7b84
+  /* Minimum required element count for resizing.  Adjusted below for
5f7b84
+     an exponential resizing policy.  */
5f7b84
+  size_t required_new_size;
5f7b84
+  if (__builtin_add_overflow (ns->_ns_main_searchlist->r_nlist,
5f7b84
+			      ns->_ns_global_scope_pending_adds,
5f7b84
+			      &required_new_size))
5f7b84
+    add_to_global_resize_failure (new);
5f7b84
+
5f7b84
   if (ns->_ns_global_scope_alloc == 0)
5f7b84
     {
5f7b84
-      /* This is the first dynamic object given global scope.  */
5f7b84
-      ns->_ns_global_scope_alloc
5f7b84
-	= ns->_ns_main_searchlist->r_nlist + to_add + 8;
5f7b84
-      new_global = (struct link_map **)
5f7b84
-	malloc (ns->_ns_global_scope_alloc * sizeof (struct link_map *));
5f7b84
-      if (new_global == NULL)
5f7b84
-	{
5f7b84
-	  ns->_ns_global_scope_alloc = 0;
5f7b84
-	nomem:
5f7b84
-	  _dl_signal_error (ENOMEM, new->l_libname->name, NULL,
5f7b84
-			    N_("cannot extend global scope"));
5f7b84
-	  return 1;
5f7b84
-	}
5f7b84
+      if (__builtin_add_overflow (required_new_size, 8, &new_size))
5f7b84
+	add_to_global_resize_failure (new);
5f7b84
+    }
5f7b84
+  else if (required_new_size > ns->_ns_global_scope_alloc)
5f7b84
+    {
5f7b84
+      if (__builtin_mul_overflow (required_new_size, 2, &new_size))
5f7b84
+	add_to_global_resize_failure (new);
5f7b84
 
5f7b84
-      /* Copy over the old entries.  */
5f7b84
-      ns->_ns_main_searchlist->r_list
5f7b84
-	= memcpy (new_global, ns->_ns_main_searchlist->r_list,
5f7b84
-		  (ns->_ns_main_searchlist->r_nlist
5f7b84
-		   * sizeof (struct link_map *)));
5f7b84
+      /* The old array was allocated with our malloc, not the minimal
5f7b84
+	 malloc.  */
5f7b84
+      old_global = ns->_ns_main_searchlist->r_list;
5f7b84
     }
5f7b84
-  else if (ns->_ns_main_searchlist->r_nlist + to_add
5f7b84
-	   > ns->_ns_global_scope_alloc)
5f7b84
+
5f7b84
+  if (new_size > 0)
5f7b84
     {
5f7b84
-      /* We have to extend the existing array of link maps in the
5f7b84
-	 main map.  */
5f7b84
-      struct link_map **old_global
5f7b84
-	= GL(dl_ns)[new->l_ns]._ns_main_searchlist->r_list;
5f7b84
-      size_t new_nalloc = ((ns->_ns_global_scope_alloc + to_add) * 2);
5f7b84
-
5f7b84
-      new_global = (struct link_map **)
5f7b84
-	malloc (new_nalloc * sizeof (struct link_map *));
5f7b84
+      size_t allocation_size;
5f7b84
+      if (__builtin_mul_overflow (new_size, sizeof (struct link_map *),
5f7b84
+				  &allocation_size))
5f7b84
+	add_to_global_resize_failure (new);
5f7b84
+      struct link_map **new_global = malloc (allocation_size);
5f7b84
       if (new_global == NULL)
5f7b84
-	goto nomem;
5f7b84
+	add_to_global_resize_failure (new);
5f7b84
 
5f7b84
-      memcpy (new_global, old_global,
5f7b84
-	      ns->_ns_global_scope_alloc * sizeof (struct link_map *));
5f7b84
+      /* Copy over the old entries.  */
5f7b84
+      memcpy (new_global, ns->_ns_main_searchlist->r_list,
5f7b84
+	      ns->_ns_main_searchlist->r_nlist * sizeof (struct link_map *));
5f7b84
 
5f7b84
-      ns->_ns_global_scope_alloc = new_nalloc;
5f7b84
+      ns->_ns_global_scope_alloc = new_size;
5f7b84
       ns->_ns_main_searchlist->r_list = new_global;
5f7b84
 
5f7b84
       if (!RTLD_SINGLE_THREAD_P)
5f7b84
@@ -131,16 +151,28 @@ add_to_global (struct link_map *new)
5f7b84
 
5f7b84
       free (old_global);
5f7b84
     }
5f7b84
+}
5f7b84
+
5f7b84
+/* Actually add the new global objects to the global scope.  Must be
5f7b84
+   called after add_to_global_resize.  This function cannot fail.  */
5f7b84
+static void
5f7b84
+add_to_global_update (struct link_map *new)
5f7b84
+{
5f7b84
+  struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
5f7b84
 
5f7b84
   /* Now add the new entries.  */
5f7b84
   unsigned int new_nlist = ns->_ns_main_searchlist->r_nlist;
5f7b84
-  for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
5f7b84
+  for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
5f7b84
     {
5f7b84
       struct link_map *map = new->l_searchlist.r_list[cnt];
5f7b84
 
5f7b84
       if (map->l_global == 0)
5f7b84
 	{
5f7b84
 	  map->l_global = 1;
5f7b84
+
5f7b84
+	  /* The array has been resized by add_to_global_resize.  */
5f7b84
+	  assert (new_nlist < ns->_ns_global_scope_alloc);
5f7b84
+
5f7b84
 	  ns->_ns_main_searchlist->r_list[new_nlist++] = map;
5f7b84
 
5f7b84
 	  /* We modify the global scope.  Report this.  */
5f7b84
@@ -149,10 +181,15 @@ add_to_global (struct link_map *new)
5f7b84
 			      map->l_name, map->l_ns);
5f7b84
 	}
5f7b84
     }
5f7b84
+
5f7b84
+  /* Some of the pending adds have been performed by the loop above.
5f7b84
+     Adjust the counter accordingly.  */
5f7b84
+  unsigned int added = new_nlist - ns->_ns_main_searchlist->r_nlist;
5f7b84
+  assert (added <= ns->_ns_global_scope_pending_adds);
5f7b84
+  ns->_ns_global_scope_pending_adds -= added;
5f7b84
+
5f7b84
   atomic_write_barrier ();
5f7b84
   ns->_ns_main_searchlist->r_nlist = new_nlist;
5f7b84
-
5f7b84
-  return 0;
5f7b84
 }
5f7b84
 
5f7b84
 /* Search link maps in all namespaces for the DSO that contains the object at
5f7b84
@@ -225,6 +262,10 @@ dl_open_worker (void *a)
5f7b84
 	args->nsid = call_map->l_ns;
5f7b84
     }
5f7b84
 
5f7b84
+  /* Retain the old value, so that it can be restored.  */
5f7b84
+  args->original_global_scope_pending_adds
5f7b84
+    = GL (dl_ns)[args->nsid]._ns_global_scope_pending_adds;
5f7b84
+
5f7b84
   /* One might be tempted to assert that we are RT_CONSISTENT at this point, but that
5f7b84
      may not be true if this is a recursive call to dlopen.  */
5f7b84
   _dl_debug_initialize (0, args->nsid);
5f7b84
@@ -266,7 +307,10 @@ dl_open_worker (void *a)
5f7b84
       /* If the user requested the object to be in the global namespace
5f7b84
 	 but it is not so far, add it now.  */
5f7b84
       if ((mode & RTLD_GLOBAL) && new->l_global == 0)
5f7b84
-	(void) add_to_global (new);
5f7b84
+	{
5f7b84
+	  add_to_global_resize (new);
5f7b84
+	  add_to_global_update (new);
5f7b84
+	}
5f7b84
 
5f7b84
       assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
5f7b84
 
5f7b84
@@ -523,6 +567,11 @@ TLS generation counter wrapped!  Please report this."));
5f7b84
   DL_STATIC_INIT (new);
5f7b84
 #endif
5f7b84
 
5f7b84
+  /* Perform the necessary allocations for adding new global objects
5f7b84
+     to the global scope below, via add_to_global_update.  */
5f7b84
+  if (mode & RTLD_GLOBAL)
5f7b84
+    add_to_global_resize (new);
5f7b84
+
5f7b84
   /* Run the initializer functions of new objects.  Temporarily
5f7b84
      disable the exception handler, so that lazy binding failures are
5f7b84
      fatal.  */
5f7b84
@@ -539,10 +588,7 @@ TLS generation counter wrapped!  Please report this."));
5f7b84
 
5f7b84
   /* Now we can make the new map available in the global scope.  */
5f7b84
   if (mode & RTLD_GLOBAL)
5f7b84
-    /* Move the object in the global namespace.  */
5f7b84
-    if (add_to_global (new) != 0)
5f7b84
-      /* It failed.  */
5f7b84
-      return;
5f7b84
+    add_to_global_update (new);
5f7b84
 
5f7b84
 #ifndef SHARED
5f7b84
   /* We must be the static _dl_open in libc.a.  A static program that
5f7b84
@@ -556,7 +602,6 @@ TLS generation counter wrapped!  Please report this."));
5f7b84
 		      new->l_name, new->l_ns, new->l_direct_opencount);
5f7b84
 }
5f7b84
 
5f7b84
-
5f7b84
 void *
5f7b84
 _dl_open (const char *file, int mode, const void *caller_dlopen, Lmid_t nsid,
5f7b84
 	  int argc, char *argv[], char *env[])
5f7b84
@@ -624,6 +669,19 @@ no more namespaces available for dlmopen()"));
5f7b84
   _dl_unload_cache ();
5f7b84
 #endif
5f7b84
 
5f7b84
+  /* Do this for both the error and success cases.  The old value has
5f7b84
+     only been determined if the namespace ID was assigned (i.e., it
5f7b84
+     is not __LM_ID_CALLER).  In the success case, we actually may
5f7b84
+     have consumed more pending adds than planned (because the local
5f7b84
+     scopes overlap in case of a recursive dlopen, the inner dlopen
5f7b84
+     doing some of the globalization work of the outer dlopen), so the
5f7b84
+     old pending adds value is larger than absolutely necessary.
5f7b84
+     Since it is just a conservative upper bound, this is harmless.
5f7b84
+     The top-level dlopen call will restore the field to zero.  */
5f7b84
+  if (args.nsid >= 0)
5f7b84
+    GL (dl_ns)[args.nsid]._ns_global_scope_pending_adds
5f7b84
+      = args.original_global_scope_pending_adds;
5f7b84
+
5f7b84
   /* See if an error occurred during loading.  */
5f7b84
   if (__glibc_unlikely (exception.errstring != NULL))
5f7b84
     {
5f7b84
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
5f7b84
index 6c5298a80bff8e96..57fbefea3cb841e9 100644
5f7b84
--- a/sysdeps/generic/ldsodefs.h
5f7b84
+++ b/sysdeps/generic/ldsodefs.h
5f7b84
@@ -311,7 +311,14 @@ struct rtld_global
5f7b84
     /* This is zero at program start to signal that the global scope map is
5f7b84
        allocated by rtld.  Later it keeps the size of the map.  It might be
5f7b84
        reset if in _dl_close if the last global object is removed.  */
5f7b84
-    size_t _ns_global_scope_alloc;
5f7b84
+    unsigned int _ns_global_scope_alloc;
5f7b84
+
5f7b84
+    /* During dlopen, this is the number of objects that still need to
5f7b84
+       be added to the global scope map.  It has to be taken into
5f7b84
+       account when resizing the map, for future map additions after
5f7b84
+       recursive dlopen calls from ELF constructors.  */
5f7b84
+    unsigned int _ns_global_scope_pending_adds;
5f7b84
+
5f7b84
     /* Search table for unique objects.  */
5f7b84
     struct unique_sym_table
5f7b84
     {