491 lines
16 KiB
Diff
491 lines
16 KiB
Diff
commit a509eb117fac1d764b15eba64993f4bdb63d7f3c
|
|
Author: Florian Weimer <fweimer@redhat.com>
|
|
Date: Wed Nov 27 16:37:17 2019 +0100
|
|
|
|
Avoid late dlopen failure due to scope, TLS slotinfo updates [BZ #25112]
|
|
|
|
This change splits the scope and TLS slotinfo updates in dlopen into
|
|
two parts: one to resize the data structures, and one to actually apply
|
|
the update. The call to add_to_global_resize in dl_open_worker is moved
|
|
before the demarcation point at which no further memory allocations are
|
|
allowed.
|
|
|
|
_dl_add_to_slotinfo is adjusted to make the list update optional. There
|
|
is some optimization possibility here because we could grow the slotinfo
|
|
list of arrays in a single call, one the largest TLS modid is known.
|
|
|
|
This commit does not fix the fatal meory allocation failure in
|
|
_dl_update_slotinfo. Ideally, this error during dlopen should be
|
|
recoverable.
|
|
|
|
The update order of scopes and TLS data structures is retained, although
|
|
it appears to be more correct to fully initialize TLS first, and then
|
|
expose symbols in the newly loaded objects via the scope update.
|
|
|
|
Tested on x86_64-linux-gnu.
|
|
|
|
Change-Id: I240c58387dabda3ca1bcab48b02115175fa83d6c
|
|
|
|
diff --git a/elf/dl-open.c b/elf/dl-open.c
|
|
index 85db4f0ecb5f29ce..b330cff7d349224a 100644
|
|
--- a/elf/dl-open.c
|
|
+++ b/elf/dl-open.c
|
|
@@ -33,6 +33,7 @@
|
|
#include <stap-probe.h>
|
|
#include <atomic.h>
|
|
#include <libc-internal.h>
|
|
+#include <array_length.h>
|
|
|
|
#include <dl-dst.h>
|
|
#include <dl-prop.h>
|
|
@@ -214,6 +215,215 @@ _dl_find_dso_for_object (const ElfW(Addr) addr)
|
|
}
|
|
rtld_hidden_def (_dl_find_dso_for_object);
|
|
|
|
+/* Return true if NEW is found in the scope for MAP. */
|
|
+static size_t
|
|
+scope_has_map (struct link_map *map, struct link_map *new)
|
|
+{
|
|
+ size_t cnt;
|
|
+ for (cnt = 0; map->l_scope[cnt] != NULL; ++cnt)
|
|
+ if (map->l_scope[cnt] == &new->l_searchlist)
|
|
+ return true;
|
|
+ return false;
|
|
+}
|
|
+
|
|
+/* Return the length of the scope for MAP. */
|
|
+static size_t
|
|
+scope_size (struct link_map *map)
|
|
+{
|
|
+ size_t cnt;
|
|
+ for (cnt = 0; map->l_scope[cnt] != NULL; )
|
|
+ ++cnt;
|
|
+ return cnt;
|
|
+}
|
|
+
|
|
+/* Resize the scopes of depended-upon objects, so that the new object
|
|
+ can be added later without further allocation of memory. This
|
|
+ function can raise an exceptions due to malloc failure. */
|
|
+static void
|
|
+resize_scopes (struct link_map *new)
|
|
+{
|
|
+ /* If the file is not loaded now as a dependency, add the search
|
|
+ list of the newly loaded object to the scope. */
|
|
+ for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
|
+ {
|
|
+ struct link_map *imap = new->l_searchlist.r_list[i];
|
|
+
|
|
+ /* If the initializer has been called already, the object has
|
|
+ not been loaded here and now. */
|
|
+ if (imap->l_init_called && imap->l_type == lt_loaded)
|
|
+ {
|
|
+ if (scope_has_map (imap, new))
|
|
+ /* Avoid duplicates. */
|
|
+ continue;
|
|
+
|
|
+ size_t cnt = scope_size (imap);
|
|
+ if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
|
|
+ {
|
|
+ /* The l_scope array is too small. Allocate a new one
|
|
+ dynamically. */
|
|
+ size_t new_size;
|
|
+ struct r_scope_elem **newp;
|
|
+
|
|
+ if (imap->l_scope != imap->l_scope_mem
|
|
+ && imap->l_scope_max < array_length (imap->l_scope_mem))
|
|
+ {
|
|
+ /* If the current l_scope memory is not pointing to
|
|
+ the static memory in the structure, but the
|
|
+ static memory in the structure is large enough to
|
|
+ use for cnt + 1 scope entries, then switch to
|
|
+ using the static memory. */
|
|
+ new_size = array_length (imap->l_scope_mem);
|
|
+ newp = imap->l_scope_mem;
|
|
+ }
|
|
+ else
|
|
+ {
|
|
+ new_size = imap->l_scope_max * 2;
|
|
+ newp = (struct r_scope_elem **)
|
|
+ malloc (new_size * sizeof (struct r_scope_elem *));
|
|
+ if (newp == NULL)
|
|
+ _dl_signal_error (ENOMEM, "dlopen", NULL,
|
|
+ N_("cannot create scope list"));
|
|
+ }
|
|
+
|
|
+ /* Copy the array and the terminating NULL. */
|
|
+ memcpy (newp, imap->l_scope,
|
|
+ (cnt + 1) * sizeof (imap->l_scope[0]));
|
|
+ struct r_scope_elem **old = imap->l_scope;
|
|
+
|
|
+ imap->l_scope = newp;
|
|
+
|
|
+ if (old != imap->l_scope_mem)
|
|
+ _dl_scope_free (old);
|
|
+
|
|
+ imap->l_scope_max = new_size;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Second stage of resize_scopes: Add NEW to the scopes. Also print
|
|
+ debugging information about scopes if requested.
|
|
+
|
|
+ This function cannot raise an exception because all required memory
|
|
+ has been allocated by a previous call to resize_scopes. */
|
|
+static void
|
|
+update_scopes (struct link_map *new)
|
|
+{
|
|
+ for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
|
+ {
|
|
+ struct link_map *imap = new->l_searchlist.r_list[i];
|
|
+ int from_scope = 0;
|
|
+
|
|
+ if (imap->l_init_called && imap->l_type == lt_loaded)
|
|
+ {
|
|
+ if (scope_has_map (imap, new))
|
|
+ /* Avoid duplicates. */
|
|
+ continue;
|
|
+
|
|
+ size_t cnt = scope_size (imap);
|
|
+ /* Assert that resize_scopes has sufficiently enlarged the
|
|
+ array. */
|
|
+ assert (cnt + 1 < imap->l_scope_max);
|
|
+
|
|
+ /* First terminate the extended list. Otherwise a thread
|
|
+ might use the new last element and then use the garbage
|
|
+ at offset IDX+1. */
|
|
+ imap->l_scope[cnt + 1] = NULL;
|
|
+ atomic_write_barrier ();
|
|
+ imap->l_scope[cnt] = &new->l_searchlist;
|
|
+
|
|
+ from_scope = cnt;
|
|
+ }
|
|
+
|
|
+ /* Print scope information. */
|
|
+ if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
|
|
+ _dl_show_scope (imap, from_scope);
|
|
+ }
|
|
+}
|
|
+
|
|
+/* Call _dl_add_to_slotinfo with DO_ADD set to false, to allocate
|
|
+ space in GL (dl_tls_dtv_slotinfo_list). This can raise an
|
|
+ exception. The return value is true if any of the new objects use
|
|
+ TLS. */
|
|
+static bool
|
|
+resize_tls_slotinfo (struct link_map *new)
|
|
+{
|
|
+ bool any_tls = false;
|
|
+ for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
|
+ {
|
|
+ struct link_map *imap = new->l_searchlist.r_list[i];
|
|
+
|
|
+ /* Only add TLS memory if this object is loaded now and
|
|
+ therefore is not yet initialized. */
|
|
+ if (! imap->l_init_called && imap->l_tls_blocksize > 0)
|
|
+ {
|
|
+ _dl_add_to_slotinfo (imap, false);
|
|
+ any_tls = true;
|
|
+ }
|
|
+ }
|
|
+ return any_tls;
|
|
+}
|
|
+
|
|
+/* Second stage of TLS update, after resize_tls_slotinfo. This
|
|
+ function does not raise any exception. It should only be called if
|
|
+ resize_tls_slotinfo returned true. */
|
|
+static void
|
|
+update_tls_slotinfo (struct link_map *new)
|
|
+{
|
|
+ unsigned int first_static_tls = new->l_searchlist.r_nlist;
|
|
+ for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
|
+ {
|
|
+ struct link_map *imap = new->l_searchlist.r_list[i];
|
|
+
|
|
+ /* Only add TLS memory if this object is loaded now and
|
|
+ therefore is not yet initialized. */
|
|
+ if (! imap->l_init_called && imap->l_tls_blocksize > 0)
|
|
+ {
|
|
+ _dl_add_to_slotinfo (imap, true);
|
|
+
|
|
+ if (imap->l_need_tls_init
|
|
+ && first_static_tls == new->l_searchlist.r_nlist)
|
|
+ first_static_tls = i;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (__builtin_expect (++GL(dl_tls_generation) == 0, 0))
|
|
+ _dl_fatal_printf (N_("\
|
|
+TLS generation counter wrapped! Please report this."));
|
|
+
|
|
+ /* We need a second pass for static tls data, because
|
|
+ _dl_update_slotinfo must not be run while calls to
|
|
+ _dl_add_to_slotinfo are still pending. */
|
|
+ for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
|
|
+ {
|
|
+ struct link_map *imap = new->l_searchlist.r_list[i];
|
|
+
|
|
+ if (imap->l_need_tls_init
|
|
+ && ! imap->l_init_called
|
|
+ && imap->l_tls_blocksize > 0)
|
|
+ {
|
|
+ /* For static TLS we have to allocate the memory here and
|
|
+ now, but we can delay updating the DTV. */
|
|
+ imap->l_need_tls_init = 0;
|
|
+#ifdef SHARED
|
|
+ /* Update the slot information data for at least the
|
|
+ generation of the DSO we are allocating data for. */
|
|
+
|
|
+ /* FIXME: This can terminate the process on memory
|
|
+ allocation failure. It is not possible to raise
|
|
+ exceptions from this context; to fix this bug,
|
|
+ _dl_update_slotinfo would have to be split into two
|
|
+ operations, similar to resize_scopes and update_scopes
|
|
+ above. This is related to bug 16134. */
|
|
+ _dl_update_slotinfo (imap->l_tls_modid);
|
|
+#endif
|
|
+
|
|
+ GL(dl_init_static_tls) (imap);
|
|
+ assert (imap->l_need_tls_init == 0);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
/* struct dl_init_args and call_dl_init are used to call _dl_init with
|
|
exception handling disabled. */
|
|
struct dl_init_args
|
|
@@ -431,133 +641,40 @@ dl_open_worker (void *a)
|
|
relocation. */
|
|
_dl_open_check (new);
|
|
|
|
- /* If the file is not loaded now as a dependency, add the search
|
|
- list of the newly loaded object to the scope. */
|
|
- bool any_tls = false;
|
|
- unsigned int first_static_tls = new->l_searchlist.r_nlist;
|
|
- for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
|
|
- {
|
|
- struct link_map *imap = new->l_searchlist.r_list[i];
|
|
- int from_scope = 0;
|
|
+ /* This only performs the memory allocations. The actual update of
|
|
+ the scopes happens below, after failure is impossible. */
|
|
+ resize_scopes (new);
|
|
|
|
- /* If the initializer has been called already, the object has
|
|
- not been loaded here and now. */
|
|
- if (imap->l_init_called && imap->l_type == lt_loaded)
|
|
- {
|
|
- struct r_scope_elem **runp = imap->l_scope;
|
|
- size_t cnt = 0;
|
|
-
|
|
- while (*runp != NULL)
|
|
- {
|
|
- if (*runp == &new->l_searchlist)
|
|
- break;
|
|
- ++cnt;
|
|
- ++runp;
|
|
- }
|
|
-
|
|
- if (*runp != NULL)
|
|
- /* Avoid duplicates. */
|
|
- continue;
|
|
-
|
|
- if (__glibc_unlikely (cnt + 1 >= imap->l_scope_max))
|
|
- {
|
|
- /* The 'r_scope' array is too small. Allocate a new one
|
|
- dynamically. */
|
|
- size_t new_size;
|
|
- struct r_scope_elem **newp;
|
|
-
|
|
-#define SCOPE_ELEMS(imap) \
|
|
- (sizeof (imap->l_scope_mem) / sizeof (imap->l_scope_mem[0]))
|
|
+ /* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
|
|
+ structure. */
|
|
+ bool any_tls = resize_tls_slotinfo (new);
|
|
|
|
- if (imap->l_scope != imap->l_scope_mem
|
|
- && imap->l_scope_max < SCOPE_ELEMS (imap))
|
|
- {
|
|
- new_size = SCOPE_ELEMS (imap);
|
|
- newp = imap->l_scope_mem;
|
|
- }
|
|
- else
|
|
- {
|
|
- new_size = imap->l_scope_max * 2;
|
|
- newp = (struct r_scope_elem **)
|
|
- malloc (new_size * sizeof (struct r_scope_elem *));
|
|
- if (newp == NULL)
|
|
- _dl_signal_error (ENOMEM, "dlopen", NULL,
|
|
- N_("cannot create scope list"));
|
|
- }
|
|
-
|
|
- memcpy (newp, imap->l_scope, cnt * sizeof (imap->l_scope[0]));
|
|
- struct r_scope_elem **old = imap->l_scope;
|
|
-
|
|
- imap->l_scope = newp;
|
|
-
|
|
- if (old != imap->l_scope_mem)
|
|
- _dl_scope_free (old);
|
|
-
|
|
- imap->l_scope_max = new_size;
|
|
- }
|
|
-
|
|
- /* First terminate the extended list. Otherwise a thread
|
|
- might use the new last element and then use the garbage
|
|
- at offset IDX+1. */
|
|
- imap->l_scope[cnt + 1] = NULL;
|
|
- atomic_write_barrier ();
|
|
- imap->l_scope[cnt] = &new->l_searchlist;
|
|
-
|
|
- /* Print only new scope information. */
|
|
- from_scope = cnt;
|
|
- }
|
|
- /* Only add TLS memory if this object is loaded now and
|
|
- therefore is not yet initialized. */
|
|
- else if (! imap->l_init_called
|
|
- /* Only if the module defines thread local data. */
|
|
- && __builtin_expect (imap->l_tls_blocksize > 0, 0))
|
|
- {
|
|
- /* Now that we know the object is loaded successfully add
|
|
- modules containing TLS data to the slot info table. We
|
|
- might have to increase its size. */
|
|
- _dl_add_to_slotinfo (imap);
|
|
-
|
|
- if (imap->l_need_tls_init
|
|
- && first_static_tls == new->l_searchlist.r_nlist)
|
|
- first_static_tls = i;
|
|
-
|
|
- /* We have to bump the generation counter. */
|
|
- any_tls = true;
|
|
- }
|
|
-
|
|
- /* Print scope information. */
|
|
- if (__glibc_unlikely (GLRO(dl_debug_mask) & DL_DEBUG_SCOPES))
|
|
- _dl_show_scope (imap, from_scope);
|
|
- }
|
|
-
|
|
- /* Bump the generation number if necessary. */
|
|
- if (any_tls && __builtin_expect (++GL(dl_tls_generation) == 0, 0))
|
|
- _dl_fatal_printf (N_("\
|
|
-TLS generation counter wrapped! Please report this."));
|
|
-
|
|
- /* We need a second pass for static tls data, because _dl_update_slotinfo
|
|
- must not be run while calls to _dl_add_to_slotinfo are still pending. */
|
|
- for (unsigned int i = first_static_tls; i < new->l_searchlist.r_nlist; ++i)
|
|
- {
|
|
- struct link_map *imap = new->l_searchlist.r_list[i];
|
|
-
|
|
- if (imap->l_need_tls_init
|
|
- && ! imap->l_init_called
|
|
- && imap->l_tls_blocksize > 0)
|
|
- {
|
|
- /* For static TLS we have to allocate the memory here and
|
|
- now, but we can delay updating the DTV. */
|
|
- imap->l_need_tls_init = 0;
|
|
-#ifdef SHARED
|
|
- /* Update the slot information data for at least the
|
|
- generation of the DSO we are allocating data for. */
|
|
- _dl_update_slotinfo (imap->l_tls_modid);
|
|
-#endif
|
|
+ /* Perform the necessary allocations for adding new global objects
|
|
+ to the global scope below. */
|
|
+ if (mode & RTLD_GLOBAL)
|
|
+ add_to_global_resize (new);
|
|
|
|
- GL(dl_init_static_tls) (imap);
|
|
- assert (imap->l_need_tls_init == 0);
|
|
- }
|
|
- }
|
|
+ /* Demarcation point: After this, no recoverable errors are allowed.
|
|
+ All memory allocations for new objects must have happened
|
|
+ before. */
|
|
+
|
|
+ /* Second stage after resize_scopes: Actually perform the scope
|
|
+ update. After this, dlsym and lazy binding can bind to new
|
|
+ objects. */
|
|
+ update_scopes (new);
|
|
+
|
|
+ /* FIXME: It is unclear whether the order here is correct.
|
|
+ Shouldn't new objects be made available for binding (and thus
|
|
+ execution) only after there TLS data has been set up fully?
|
|
+ Fixing bug 16134 will likely make this distinction less
|
|
+ important. */
|
|
+
|
|
+ /* Second stage after resize_tls_slotinfo: Update the slotinfo data
|
|
+ structures. */
|
|
+ if (any_tls)
|
|
+ /* FIXME: This calls _dl_update_slotinfo, which aborts the process
|
|
+ on memory allocation failure. See bug 16134. */
|
|
+ update_tls_slotinfo (new);
|
|
|
|
/* Notify the debugger all new objects have been relocated. */
|
|
if (relocation_in_progress)
|
|
diff --git a/elf/dl-tls.c b/elf/dl-tls.c
|
|
index c87caf13d6a97ba4..a2def280b7096960 100644
|
|
--- a/elf/dl-tls.c
|
|
+++ b/elf/dl-tls.c
|
|
@@ -883,7 +883,7 @@ _dl_tls_get_addr_soft (struct link_map *l)
|
|
|
|
|
|
void
|
|
-_dl_add_to_slotinfo (struct link_map *l)
|
|
+_dl_add_to_slotinfo (struct link_map *l, bool do_add)
|
|
{
|
|
/* Now that we know the object is loaded successfully add
|
|
modules containing TLS data to the dtv info table. We
|
|
@@ -939,6 +939,9 @@ cannot create TLS data structures"));
|
|
}
|
|
|
|
/* Add the information into the slotinfo data structure. */
|
|
- listp->slotinfo[idx].map = l;
|
|
- listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
|
|
+ if (do_add)
|
|
+ {
|
|
+ listp->slotinfo[idx].map = l;
|
|
+ listp->slotinfo[idx].gen = GL(dl_tls_generation) + 1;
|
|
+ }
|
|
}
|
|
diff --git a/elf/rtld.c b/elf/rtld.c
|
|
index 4ec26a79cbb0aa4f..0aa1a2a19f649e16 100644
|
|
--- a/elf/rtld.c
|
|
+++ b/elf/rtld.c
|
|
@@ -2167,7 +2167,7 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
|
|
|
|
/* Add object to slot information data if necessasy. */
|
|
if (l->l_tls_blocksize != 0 && tls_init_tp_called)
|
|
- _dl_add_to_slotinfo (l);
|
|
+ _dl_add_to_slotinfo (l, true);
|
|
}
|
|
}
|
|
else
|
|
@@ -2215,7 +2215,7 @@ ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
|
|
|
|
/* Add object to slot information data if necessasy. */
|
|
if (l->l_tls_blocksize != 0 && tls_init_tp_called)
|
|
- _dl_add_to_slotinfo (l);
|
|
+ _dl_add_to_slotinfo (l, true);
|
|
}
|
|
HP_TIMING_NOW (stop);
|
|
|
|
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
|
|
index 57fbefea3cb841e9..c6b7e61badbfd513 100644
|
|
--- a/sysdeps/generic/ldsodefs.h
|
|
+++ b/sysdeps/generic/ldsodefs.h
|
|
@@ -1135,8 +1135,15 @@ extern void *_dl_open (const char *name, int mode, const void *caller,
|
|
old scope, OLD can't be freed until no thread is using it. */
|
|
extern int _dl_scope_free (void *) attribute_hidden;
|
|
|
|
-/* Add module to slot information data. */
|
|
-extern void _dl_add_to_slotinfo (struct link_map *l) attribute_hidden;
|
|
+
|
|
+/* Add module to slot information data. If DO_ADD is false, only the
|
|
+ required memory is allocated. Must be called with GL
|
|
+ (dl_load_lock) acquired. If the function has already been called
|
|
+ for the link map L with !do_add, then this function will not raise
|
|
+ an exception, otherwise it is possible that it encounters a memory
|
|
+ allocation failure. */
|
|
+extern void _dl_add_to_slotinfo (struct link_map *l, bool do_add)
|
|
+ attribute_hidden;
|
|
|
|
/* Update slot information data for at least the generation of the
|
|
module with the given index. */
|