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