Compare commits

...
Author SHA1 Message Date
Florian Weimer 8c59ed1f74 Remove all loaded objects if dlopen fails [BZ #20839] 2019-10-29 18:11:36 +01:00
Florian Weimer 93e07b2f9e 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_prepare 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.
2019-10-28 14:31:05 +01:00
Florian Weimer f0ead7b63b Avoid late failure in dlopen in global scope update [BZ #25112]
The call to add_to_global in dl_open_worker happens after running ELF
constructors for new objects.  At this point, proper recovery from
malloc failure would be quite complicated: We would have to run the
ELF destructors and close all opened objects, something that we
currently do not do.

Instead, this change splits add_to_global into two phases,
add_to_global_prepare (which can raise an exception, called before ELF
constructors run), and add_to_global_finish (which cannot, called
after ELF constructors).  A complication arises due to recursive
dlopen: After the inner dlopen consumes some space, the pre-allocation
in the outer dlopen may no longer be sufficient.  A new member in the
namespace structure, _ns_global_scope_pending_adds keeps track of the
maximum number of objects that need to be added to the global scope.
This enables the inner add_to_global_prepare call to take into account
the needs of an outer dlopen.

Most code in the dynamic linker assumes that the number of global
scope entries fits into an unsigned int (matching the r_nlist member
of struct r_scop_elem).  Therefore, change the type of
_ns_global_scope_alloc to unsigned int (from size_t), and add overflow
checks.

Tested on x86_64-linux-gnu and i686-linux-gnu.
2019-10-28 14:30:41 +01:00
Florian Weimer 7f1de8894b Lazy binding failures during dlopen/dlclose must be fatal [BZ #24304]
If a lazy binding failure happens during the execution of an ELF
constructor or destructor, the dynamic loader catches the error
and reports it using the dlerror mechanism.  This is undesirable
because there could be other constructors and destructors that
need processing (which are skipped), and the process is in an
inconsistent state at this point.  Therefore, we have to issue
a fatal dynamic loader error error and terminate the process.

To temporarily disable exception handling (turning exceptions
into fatal errors), _dl_catch_exception is changed to accept
a null exception argument, which indicates that it should call
the operate function with exception handling disabled.

_dl_fini does not need changes because it does not install an error
handler, so errors are already fatal there.

Tested on x86_64-linux-gnu and i686-linux-gnu.
2019-10-17 11:20:39 +02:00
18 changed files with 903 additions and 239 deletions
+24 -2
View File
@@ -193,7 +193,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
tst-debug1 tst-main1 tst-absolute-sym tst-absolute-zero tst-big-note \
tst-unwind-ctor tst-unwind-main tst-audit13 \
tst-sonamemove-link tst-sonamemove-dlopen tst-dlopen-tlsmodid \
tst-dlopen-self
tst-dlopen-self tst-initfinilazyfail tst-dlopenfail
# reldep9
tests-internal += loadtest unload unload2 circleload1 \
neededtest neededtest2 neededtest3 neededtest4 \
@@ -281,7 +281,9 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
tst-main1mod tst-libc_dlvsym-dso tst-absolute-sym-lib \
tst-absolute-zero-lib tst-big-note-lib tst-unwind-ctor-lib \
tst-audit13mod1 tst-sonamemove-linkmod1 \
tst-sonamemove-runmod1 tst-sonamemove-runmod2
tst-sonamemove-runmod1 tst-sonamemove-runmod2 \
tst-initlazyfailmod tst-finilazyfailmod \
tst-dlopenfailmod1 tst-dlopenfaillinkmod tst-dlopenfailmod2
# Most modules build with _ISOMAC defined, but those filtered out
# depend on internal headers.
modules-names-tests = $(filter-out ifuncmod% tst-libc_dlvsym-dso tst-tlsmod%,\
@@ -1569,3 +1571,23 @@ $(objpfx)tst-big-note-lib.so: $(objpfx)tst-big-note-lib.o
$(objpfx)tst-unwind-ctor: $(objpfx)tst-unwind-ctor-lib.so
CFLAGS-tst-unwind-main.c += -funwind-tables -DUSE_PTHREADS=0
$(objpfx)tst-initfinilazyfail: $(libdl)
$(objpfx)tst-initfinilazyfail.out: \
$(objpfx)tst-initlazyfailmod.so $(objpfx)tst-finilazyfailmod.so
# Override -z defs, so that we can reference an undefined symbol.
# Force lazy binding for the same reason.
LDFLAGS-tst-initlazyfailmod.so = \
-Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
LDFLAGS-tst-finilazyfailmod.so = \
-Wl,-z,lazy -Wl,--unresolved-symbols=ignore-all
$(objpfx)tst-dlopenfail: $(libdl)
$(objpfx)tst-dlopenfail.out: \
$(objpfx)tst-dlopenfailmod1.so $(objpfx)tst-dlopenfailmod2.so
# Order matters here. tst-dlopenfaillinkmod.so's soname ensures
# a run-time loader failure.
$(objpfx)tst-dlopenfailmod1.so: \
$(shared-thread-library) $(objpfx)tst-dlopenfaillinkmod.so
LDFLAGS-tst-dlopenfaillinkmod.so = -Wl,-soname,tst-dlopenfail-missingmod.so
$(objpfx)tst-dlopenfailmod2.so: $(shared-thread-library)
+32 -28
View File
@@ -106,6 +106,30 @@ remove_slotinfo (size_t idx, struct dtv_slotinfo_list *listp, size_t disp,
return false;
}
/* Invoke dstructors for CLOSURE (a struct link_map *). Called with
exception handling temporarily disabled, to make errors fatal. */
static void
call_destructors (void *closure)
{
struct link_map *map = closure;
if (map->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (map->l_addr
+ map->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int sz = (map->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (sz-- > 0)
((fini_t) array[sz]) ();
}
/* Next try the old-style destructor. */
if (map->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI (map, ((void *) map->l_addr
+ map->l_info[DT_FINI]->d_un.d_ptr));
}
void
_dl_close_worker (struct link_map *map, bool force)
@@ -144,14 +168,6 @@ _dl_close_worker (struct link_map *map, bool force)
char done[nloaded];
struct link_map *maps[nloaded];
/* Clear DF_1_NODELETE to force object deletion. We don't need to touch
l_tls_dtor_count because forced object deletion only happens when an
error occurs during object load. Destructor registration for TLS
non-POD objects should not have happened till then for this
object. */
if (force)
map->l_flags_1 &= ~DF_1_NODELETE;
/* Run over the list and assign indexes to the link maps and enter
them into the MAPS array. */
int idx = 0;
@@ -181,7 +197,7 @@ _dl_close_worker (struct link_map *map, bool force)
/* Check whether this object is still used. */
if (l->l_type == lt_loaded
&& l->l_direct_opencount == 0
&& (l->l_flags_1 & DF_1_NODELETE) == 0
&& l->l_nodelete != link_map_nodelete_active
/* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why
acquire is sufficient and correct. */
&& atomic_load_acquire (&l->l_tls_dtor_count) == 0
@@ -264,10 +280,11 @@ _dl_close_worker (struct link_map *map, bool force)
if (!used[i])
{
assert (imap->l_type == lt_loaded
&& (imap->l_flags_1 & DF_1_NODELETE) == 0);
&& imap->l_nodelete != link_map_nodelete_active);
/* Call its termination function. Do not do it for
half-cooked objects. */
half-cooked objects. Temporarily disable exception
handling, so that errors are fatal. */
if (imap->l_init_called)
{
/* When debugging print a message first. */
@@ -276,22 +293,9 @@ _dl_close_worker (struct link_map *map, bool force)
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
imap->l_name, nsid);
if (imap->l_info[DT_FINI_ARRAY] != NULL)
{
ElfW(Addr) *array =
(ElfW(Addr) *) (imap->l_addr
+ imap->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int sz = (imap->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (sz-- > 0)
((fini_t) array[sz]) ();
}
/* Next try the old-style destructor. */
if (imap->l_info[DT_FINI] != NULL)
DL_CALL_DT_FINI (imap, ((void *) imap->l_addr
+ imap->l_info[DT_FINI]->d_un.d_ptr));
if (imap->l_info[DT_FINI_ARRAY] != NULL
|| imap->l_info[DT_FINI] != NULL)
_dl_catch_exception (NULL, call_destructors, imap);
}
#ifdef SHARED
@@ -816,7 +820,7 @@ _dl_close (void *_map)
before we took the lock. There is no way to detect this (see below)
so we proceed assuming this isn't the case. First see whether we
can remove the object at all. */
if (__glibc_unlikely (map->l_flags_1 & DF_1_NODELETE))
if (__glibc_unlikely (map->l_nodelete == link_map_nodelete_active))
{
/* Nope. Do nothing. */
__rtld_lock_unlock_recursive (GL(dl_load_lock));
+12
View File
@@ -173,6 +173,18 @@ int
_dl_catch_exception (struct dl_exception *exception,
void (*operate) (void *), void *args)
{
/* If exception is NULL, temporarily disable exception handling.
Exceptions during operate (args) are fatal. */
if (exception == NULL)
{
struct catch *const old = catch_hook;
catch_hook = NULL;
operate (args);
/* If we get here, the operation was successful. */
catch_hook = old;
return 0;
}
/* We need not handle `receiver' since setting a `catch' is handled
before it. */
+61 -19
View File
@@ -192,9 +192,10 @@ enter_unique_sym (struct unique_sym *table, size_t size,
Return the matching symbol in RESULT. */
static void
do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
const struct link_map *map, struct sym_val *result,
struct link_map *map, struct sym_val *result,
int type_class, const ElfW(Sym) *sym, const char *strtab,
const ElfW(Sym) *ref, const struct link_map *undef_map)
const ElfW(Sym) *ref, const struct link_map *undef_map,
int flags)
{
/* We have to determine whether we already found a symbol with this
name before. If not then we have to add it to the search table.
@@ -222,7 +223,7 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
copy from the copy addressed through the
relocation. */
result->s = sym;
result->m = (struct link_map *) map;
result->m = map;
}
else
{
@@ -311,9 +312,19 @@ do_lookup_unique (const char *undef_name, uint_fast32_t new_hash,
new_hash, strtab + sym->st_name, sym, map);
if (map->l_type == lt_loaded)
/* Make sure we don't unload this object by
setting the appropriate flag. */
((struct link_map *) map)->l_flags_1 |= DF_1_NODELETE;
{
/* Make sure we don't unload this object by
setting the appropriate flag. */
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
&& map->l_nodelete == link_map_nodelete_inactive)
_dl_debug_printf ("\
marking %s [%lu] as NODELETE due to unique symbol\n",
map->l_name, map->l_ns);
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
map->l_nodelete = link_map_nodelete_pending;
else
map->l_nodelete = link_map_nodelete_active;
}
}
++tab->n_elements;
@@ -525,8 +536,9 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
return 1;
case STB_GNU_UNIQUE:;
do_lookup_unique (undef_name, new_hash, map, result, type_class,
sym, strtab, ref, undef_map);
do_lookup_unique (undef_name, new_hash, (struct link_map *) map,
result, type_class, sym, strtab, ref,
undef_map, flags);
return 1;
default:
@@ -568,9 +580,13 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
if (undef_map == map)
return 0;
/* Avoid references to objects which cannot be unloaded anyway. */
/* Avoid references to objects which cannot be unloaded anyway. We
do not need to record dependencies if this object goes away
during dlopen failure, either. IFUNC resolvers with relocation
dependencies may pick an dependency which can be dlclose'd, but
such IFUNC resolvers are undefined anyway. */
assert (map->l_type == lt_loaded);
if ((map->l_flags_1 & DF_1_NODELETE) != 0)
if (map->l_nodelete != link_map_nodelete_inactive)
return 0;
struct link_map_reldeps *l_reldeps
@@ -678,16 +694,33 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
/* Redo the NODELETE check, as when dl_load_lock wasn't held
yet this could have changed. */
if ((map->l_flags_1 & DF_1_NODELETE) != 0)
if (map->l_nodelete != link_map_nodelete_inactive)
goto out;
/* If the object with the undefined reference cannot be removed ever
just make sure the same is true for the object which contains the
definition. */
if (undef_map->l_type != lt_loaded
|| (undef_map->l_flags_1 & DF_1_NODELETE) != 0)
|| (undef_map->l_nodelete != link_map_nodelete_inactive))
{
map->l_flags_1 |= DF_1_NODELETE;
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
&& map->l_nodelete == link_map_nodelete_inactive)
{
if (undef_map->l_name[0] == '\0')
_dl_debug_printf ("\
marking %s [%lu] as NODELETE due to reference to main program\n",
map->l_name, map->l_ns);
else
_dl_debug_printf ("\
marking %s [%lu] as NODELETE due to reference to %s [%lu]\n",
map->l_name, map->l_ns,
undef_map->l_name, undef_map->l_ns);
}
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
map->l_nodelete = link_map_nodelete_pending;
else
map->l_nodelete = link_map_nodelete_active;
goto out;
}
@@ -712,7 +745,18 @@ add_dependency (struct link_map *undef_map, struct link_map *map, int flags)
no fatal problem. We simply make sure the referenced object
cannot be unloaded. This is semantically the correct
behavior. */
map->l_flags_1 |= DF_1_NODELETE;
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)
&& map->l_nodelete == link_map_nodelete_inactive)
_dl_debug_printf ("\
marking %s [%lu] as NODELETE due to memory allocation failure\n",
map->l_name, map->l_ns);
if (flags & DL_LOOKUP_INSIDE_DLOPEN)
/* In case of non-lazy binding, we could actually
report the memory allocation error, but for now, we
use the conservative approximation as well. */
map->l_nodelete = link_map_nodelete_pending;
else
map->l_nodelete = link_map_nodelete_active;
goto out;
}
else
@@ -792,11 +836,9 @@ _dl_lookup_symbol_x (const char *undef_name, struct link_map *undef_map,
bump_num_relocations ();
/* No other flag than DL_LOOKUP_ADD_DEPENDENCY or DL_LOOKUP_GSCOPE_LOCK
is allowed if we look up a versioned symbol. */
assert (version == NULL
|| (flags & ~(DL_LOOKUP_ADD_DEPENDENCY | DL_LOOKUP_GSCOPE_LOCK))
== 0);
/* DL_LOOKUP_RETURN_NEWEST does not make sense for versioned
lookups. */
assert (version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST));
size_t i = 0;
if (__glibc_unlikely (skip_map != NULL))
+420 -180
View File
@@ -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>
@@ -50,22 +51,38 @@ struct dl_open_args
struct link_map *map;
/* Namespace ID. */
Lmid_t nsid;
/* Original value of _ns_global_scope_pending_adds. Set by
dl_open_worker. Only valid if nsid is a real namespace
(non-negative). */
unsigned int original_global_scope_pending_adds;
/* Original parameters to the program and the current environment. */
int argc;
char **argv;
char **env;
};
static int
add_to_global (struct link_map *new)
/* Called in case the global scope cannot be extended. */
static void __attribute__ ((noreturn))
add_to_global_prepare_failure (struct link_map *new)
{
struct link_map **new_global;
unsigned int to_add = 0;
unsigned int cnt;
_dl_signal_error (ENOMEM, new->l_libname->name, NULL,
N_ ("cannot extend global scope"));
}
/* Grow the global scope array for the namespace, so that all the new
global objects can be added later in add_to_global_finish, without
risk of memory allocation failure. add_to_global_prepare raises
exceptions for memory allocation errors. */
static void
add_to_global_prepare (struct link_map *new)
{
struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
/* Count the objects we have to put in the global scope. */
for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
unsigned int to_add = 0;
for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
if (new->l_searchlist.r_list[cnt]->l_global == 0)
++to_add;
@@ -83,47 +100,51 @@ add_to_global (struct link_map *new)
in an realloc() call. Therefore we allocate a completely new
array the first time we have to add something to the locale scope. */
struct link_namespaces *ns = &GL(dl_ns)[new->l_ns];
if (__builtin_add_overflow (ns->_ns_global_scope_pending_adds, to_add,
&ns->_ns_global_scope_pending_adds))
add_to_global_prepare_failure (new);
unsigned int new_size = 0; /* 0 means no new allocation. */
void *old_global = NULL; /* Old allocation if free-able. */
/* Minimum required element count for resizing. Adjusted below for
an exponential resizing policy. */
size_t required_new_size;
if (__builtin_add_overflow (ns->_ns_main_searchlist->r_nlist,
ns->_ns_global_scope_pending_adds,
&required_new_size))
add_to_global_prepare_failure (new);
if (ns->_ns_global_scope_alloc == 0)
{
/* This is the first dynamic object given global scope. */
ns->_ns_global_scope_alloc
= ns->_ns_main_searchlist->r_nlist + to_add + 8;
new_global = (struct link_map **)
malloc (ns->_ns_global_scope_alloc * sizeof (struct link_map *));
if (__builtin_add_overflow (required_new_size, 8, &new_size))
add_to_global_prepare_failure (new);
}
else if (required_new_size > ns->_ns_global_scope_alloc)
{
if (__builtin_mul_overflow (required_new_size, 2, &new_size))
add_to_global_prepare_failure (new);
/* The old array was allocated with our malloc, not the minimal
malloc. */
old_global = ns->_ns_main_searchlist->r_list;
}
if (new_size > 0)
{
size_t allocation_size;
if (__builtin_mul_overflow (new_size, sizeof (struct link_map *),
&allocation_size))
add_to_global_prepare_failure (new);
struct link_map **new_global = malloc (allocation_size);
if (new_global == NULL)
{
ns->_ns_global_scope_alloc = 0;
nomem:
_dl_signal_error (ENOMEM, new->l_libname->name, NULL,
N_("cannot extend global scope"));
return 1;
}
add_to_global_prepare_failure (new);
/* Copy over the old entries. */
ns->_ns_main_searchlist->r_list
= memcpy (new_global, ns->_ns_main_searchlist->r_list,
(ns->_ns_main_searchlist->r_nlist
* sizeof (struct link_map *)));
}
else if (ns->_ns_main_searchlist->r_nlist + to_add
> ns->_ns_global_scope_alloc)
{
/* We have to extend the existing array of link maps in the
main map. */
struct link_map **old_global
= GL(dl_ns)[new->l_ns]._ns_main_searchlist->r_list;
size_t new_nalloc = ((ns->_ns_global_scope_alloc + to_add) * 2);
memcpy (new_global, ns->_ns_main_searchlist->r_list,
ns->_ns_main_searchlist->r_nlist * sizeof (struct link_map *));
new_global = (struct link_map **)
malloc (new_nalloc * sizeof (struct link_map *));
if (new_global == NULL)
goto nomem;
memcpy (new_global, old_global,
ns->_ns_global_scope_alloc * sizeof (struct link_map *));
ns->_ns_global_scope_alloc = new_nalloc;
ns->_ns_global_scope_alloc = new_size;
ns->_ns_main_searchlist->r_list = new_global;
if (!RTLD_SINGLE_THREAD_P)
@@ -131,16 +152,28 @@ add_to_global (struct link_map *new)
free (old_global);
}
}
/* Actually add the new global objects to the global scope. Must be
called after add_to_global_prepare. This function cannot fail. */
static void
add_to_global_finish (struct link_map *new)
{
struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
/* Now add the new entries. */
unsigned int new_nlist = ns->_ns_main_searchlist->r_nlist;
for (cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
{
struct link_map *map = new->l_searchlist.r_list[cnt];
if (map->l_global == 0)
{
map->l_global = 1;
/* The array has been resized by add_to_global_prepare. */
assert (new_nlist < ns->_ns_global_scope_alloc);
ns->_ns_main_searchlist->r_list[new_nlist++] = map;
/* We modify the global scope. Report this. */
@@ -149,10 +182,24 @@ add_to_global (struct link_map *new)
map->l_name, map->l_ns);
}
}
/* Some of the pending adds have been performed by the loop above.
Adjust the counter accordingly. */
unsigned int added = new_nlist - ns->_ns_main_searchlist->r_nlist;
assert (added <= ns->_ns_global_scope_pending_adds);
ns->_ns_global_scope_pending_adds -= added;
atomic_write_barrier ();
ns->_ns_main_searchlist->r_nlist = new_nlist;
}
return 0;
/* Combination of add_to_global_prepare and add_to_global_finish.
(This can signal exceptions via add_to_global_prepare.) */
static void
add_to_global (struct link_map *new)
{
add_to_global_prepare (new);
add_to_global_finish (new);
}
/* Search link maps in all namespaces for the DSO that contains the object at
@@ -177,6 +224,247 @@ _dl_find_dso_for_object (const ElfW(Addr) addr)
}
rtld_hidden_def (_dl_find_dso_for_object);
/* Returned from scope_size if the new map was found in the existing
scope. */
enum { scope_size_found_new = (size_t) -1 };
/* Return the length of the scope for MAP. If NEW->l_searchlist is
found in the scope, return scope_size_found_new. */
static size_t
scope_size (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 scope_size_found_new;
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)
{
size_t cnt = scope_size (imap, new);
if (cnt == scope_size_found_new)
/* 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;
if (imap->l_scope != imap->l_scope_mem
&& imap->l_scope_max < array_length (imap->l_scope_mem))
{
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)
{
size_t cnt = scope_size (imap, new);
if (cnt == scope_size_found_new)
/* Avoid duplicates. */
continue;
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. */
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. */
_dl_update_slotinfo (imap->l_tls_modid);
#endif
GL(dl_init_static_tls) (imap);
assert (imap->l_need_tls_init == 0);
}
}
}
/* Mark the objects as NODELETE if required. This is delayed until
after dlopen failure is not possible, so that _dl_close can clean
up objects if necessary. */
static void
activate_nodelete (struct link_map *new, int mode)
{
if (mode & RTLD_NODELETE || new->l_nodelete == link_map_nodelete_pending)
{
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
new->l_name, new->l_ns);
new->l_nodelete = link_map_nodelete_active;
}
/* Update all objects in our scope. Any of them could have been
promoted to NODELETE status due to new relocations. */
for (unsigned int i = 0; i < new->l_searchlist.r_nlist; ++i)
{
struct link_map *imap = new->l_searchlist.r_list[i];
if (imap->l_nodelete == link_map_nodelete_pending)
{
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES))
_dl_debug_printf ("activating NODELETE for %s [%lu]\n",
imap->l_name, imap->l_ns);
imap->l_nodelete = link_map_nodelete_active;
}
}
}
/* struct dl_init_args and call_dl_init are used to call _dl_init with
exception handling disabled. */
struct dl_init_args
{
struct link_map *new;
int argc;
char **argv;
char **env;
};
static void
call_dl_init (void *closure)
{
struct dl_init_args *args = closure;
_dl_init (args->new, args->argc, args->argv, args->env);
}
static void
dl_open_worker (void *a)
{
@@ -208,6 +496,10 @@ dl_open_worker (void *a)
args->nsid = call_map->l_ns;
}
/* Retain the old value, so that it can be restored. */
args->original_global_scope_pending_adds
= GL (dl_ns)[args->nsid]._ns_global_scope_pending_adds;
/* One might be tempted to assert that we are RT_CONSISTENT at this point, but that
may not be true if this is a recursive call to dlopen. */
_dl_debug_initialize (0, args->nsid);
@@ -225,12 +517,6 @@ dl_open_worker (void *a)
return;
}
/* Mark the object as not deletable if the RTLD_NODELETE flags was passed.
Do this early so that we don't skip marking the object if it was
already loaded. */
if (__glibc_unlikely (mode & RTLD_NODELETE))
new->l_flags_1 |= DF_1_NODELETE;
if (__glibc_unlikely (mode & __RTLD_SPROF))
/* This happens only if we load a DSO for 'sprof'. */
return;
@@ -246,16 +532,33 @@ dl_open_worker (void *a)
_dl_debug_printf ("opening file=%s [%lu]; direct_opencount=%u\n\n",
new->l_name, new->l_ns, new->l_direct_opencount);
/* If the user requested the object to be in the global namespace
but it is not so far, add it now. */
/* If the user requested the object to be in the global
namespace but it is not so far, add it now. This can raise
an exception to do a malloc failure. */
if ((mode & RTLD_GLOBAL) && new->l_global == 0)
(void) add_to_global (new);
add_to_global (new);
/* Mark the object as not deletable if the RTLD_NODELETE flags
was passed. */
if (__glibc_unlikely (mode & RTLD_NODELETE))
{
if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_FILES)
&& new->l_nodelete == link_map_nodelete_inactive)
_dl_debug_printf ("marking %s [%lu] as NODELETE\n",
new->l_name, new->l_ns);
new->l_nodelete = link_map_nodelete_active;
}
assert (_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT);
return;
}
/* Schedule NODELETE marking for the directly loaded object if
requested. */
if (__glibc_unlikely (mode & RTLD_NODELETE))
new->l_nodelete = link_map_nodelete_pending;
/* Load that object's dependencies. */
_dl_map_object_deps (new, NULL, 0, 0,
mode & (__RTLD_DLOPEN | RTLD_DEEPBIND | __RTLD_AUDIT));
@@ -327,6 +630,14 @@ dl_open_worker (void *a)
int relocation_in_progress = 0;
/* Perform relocation. This can trigger lazy binding in IFUNC
resolvers. For NODELETE mappings, these dependencies are not
recorded because the flag has not been applied to the newly
loaded objects. This means that upon dlopen failure, these
NODELETE objects can be unloaded despite existing references to
them. However, such relocation dependencies in IFUNC resolvers
are undefined anyway, so this is not a problem. */
for (unsigned int i = nmaps; i-- > 0; )
{
l = maps[i];
@@ -356,7 +667,7 @@ dl_open_worker (void *a)
_dl_start_profile ();
/* Prevent unloading the object. */
GL(dl_profile_map)->l_flags_1 |= DF_1_NODELETE;
GL(dl_profile_map)->l_nodelete = link_map_nodelete_active;
}
}
else
@@ -370,133 +681,41 @@ 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;
/* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
structure. */
bool any_tls = resize_tls_slotinfo (new);
while (*runp != NULL)
{
if (*runp == &new->l_searchlist)
break;
++cnt;
++runp;
}
/* Perform the necessary allocations for adding new global objects
to the global scope below. */
if (mode & RTLD_GLOBAL)
add_to_global_prepare (new);
if (*runp != NULL)
/* Avoid duplicates. */
continue;
/* Demarcation point: After this, no recoverable errors are allowed.
All memory allocations for new objects must have happened
before. */
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;
activate_nodelete (new, mode);
#define SCOPE_ELEMS(imap) \
(sizeof (imap->l_scope_mem) / sizeof (imap->l_scope_mem[0]))
/* Second stage after resize_scopes: Actually perform the scope
update. After this, dlsym and lazy binding can bind to new
objects. */
update_scopes (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"));
}
/* 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
correctly? */
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
GL(dl_init_static_tls) (imap);
assert (imap->l_need_tls_init == 0);
}
}
/* 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. */
update_tls_slotinfo (new);
/* Notify the debugger all new objects have been relocated. */
if (relocation_in_progress)
@@ -506,15 +725,23 @@ TLS generation counter wrapped! Please report this."));
DL_STATIC_INIT (new);
#endif
/* Run the initializer functions of new objects. */
_dl_init (new, args->argc, args->argv, args->env);
/* Run the initializer functions of new objects. Temporarily
disable the exception handler, so that lazy binding failures are
fatal. */
{
struct dl_init_args init_args =
{
.new = new,
.argc = args->argc,
.argv = args->argv,
.env = args->env
};
_dl_catch_exception (NULL, call_dl_init, &init_args);
}
/* Now we can make the new map available in the global scope. */
if (mode & RTLD_GLOBAL)
/* Move the object in the global namespace. */
if (add_to_global (new) != 0)
/* It failed. */
return;
add_to_global_finish (new);
#ifndef SHARED
/* We must be the static _dl_open in libc.a. A static program that
@@ -596,6 +823,19 @@ no more namespaces available for dlmopen()"));
_dl_unload_cache ();
#endif
/* Do this for both the success and failure cases. The old value
has only been determined if the namespace ID was assigned (i.e.,
it is not __LM_ID_CALLER). In the success case, we actually may
have consumed more pending adds than planned (because the local
scopes overlap in case of a recursive dlopen, the inner dlopen
doing some of the globalization work of the outer dlopen), so the
old pending adds value is larger than absolutely necessary.
Since it is just a conservative upper bound, this is harmless.
The top-level dlopen call will restore the field to zero. */
if (args.nsid >= 0)
GL (dl_ns)[args.nsid]._ns_global_scope_pending_adds
= args.original_global_scope_pending_adds;
/* See if an error occurred during loading. */
if (__glibc_unlikely (exception.errstring != NULL))
{
+2 -1
View File
@@ -244,7 +244,8 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[],
v = (version); \
_lr = _dl_lookup_symbol_x (strtab + (*ref)->st_name, l, (ref), \
scope, v, _tc, \
DL_LOOKUP_ADD_DEPENDENCY, NULL); \
DL_LOOKUP_ADD_DEPENDENCY \
| DL_LOOKUP_INSIDE_DLOPEN, NULL); \
l->l_lookup_cache.ret = (*ref); \
l->l_lookup_cache.value = _lr; })) \
: l)
+6 -3
View File
@@ -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;
}
}
+2
View File
@@ -163,6 +163,8 @@ elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
if (info[VERSYMIDX (DT_FLAGS_1)] != NULL)
{
l->l_flags_1 = info[VERSYMIDX (DT_FLAGS_1)]->d_un.d_val;
if (l->l_flags_1 & DF_1_NODELETE)
l->l_nodelete = link_map_nodelete_pending;
/* Only DT_1_SUPPORTED_MASK bits are supported, and we would like
to assert this, but we can't. Users have been setting
+2 -2
View File
@@ -2214,7 +2214,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
/* 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
@@ -2259,7 +2259,7 @@ ERROR: '%s': cannot process note segment.\n", _dl_argv[0]);
/* 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);
}
rtld_timer_stop (&relocate_time, start);
+79
View File
@@ -0,0 +1,79 @@
/* Test dlopen rollback after failures involving NODELETE objects (bug 20839).
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <dlfcn.h>
#include <errno.h>
#include <gnu/lib-names.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <support/check.h>
#include <support/xdlfcn.h>
static int
do_test (void)
{
/* This test uses libpthread as the canonical NODELETE module. If
libpthread is no longer NODELETE because it has been merged into
libc, the test needs to be updated. */
TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
/* This is expected to fail because of the missing dependency. */
puts ("info: attempting to load tst-dlopenfailmod1.so");
TEST_VERIFY (dlopen ("tst-dlopenfailmod1.so", RTLD_LAZY) == NULL);
const char *message = dlerror ();
TEST_COMPARE_STRING (message,
"tst-dlopenfail-missingmod.so:"
" cannot open shared object file:"
" No such file or directory");
/* Do not probe for the presence of libpthread at this point because
that might trigger relocation if bug 20839 is present, obscuring
a subsequent crash. */
/* This is expected to succeed. */
puts ("info: loading tst-dlopenfailmod2.so");
void *handle = xdlopen ("tst-dlopenfailmod2.so", RTLD_NOW);
xdlclose (handle);
/* libpthread should remain loaded. */
TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_NOLOAD) != NULL);
TEST_VERIFY (dlsym (NULL, "pthread_create") == NULL);
/* We can make libpthread global, and then the symbol should become
available. */
TEST_VERIFY (dlopen (LIBPTHREAD_SO, RTLD_LAZY | RTLD_GLOBAL) != NULL);
TEST_VERIFY (dlsym (NULL, "pthread_create") != NULL);
/* sem_open is sufficiently complex to depend on relocations. */
void *(*sem_open_ptr) (const char *, int flag, ...)
= dlsym (NULL, "sem_open");
if (sem_open_ptr == NULL)
/* Hurd does not implement sem_open. */
puts ("warning: sem_open not found, further testing not possible");
else
{
errno = 0;
TEST_VERIFY (sem_open_ptr ("/", 0) == NULL);
TEST_COMPARE (errno, EINVAL);
}
return 0;
}
#include <support/test-driver.c>
+17
View File
@@ -0,0 +1,17 @@
/* Empty module with a soname which is not available at run time.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
+36
View File
@@ -0,0 +1,36 @@
/* Module which depends on two modules: one NODELETE, one missing.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
/* Note: Due to the missing second module, this object cannot be
loaded at run time. */
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
/* Force linking against libpthread. */
void *pthread_create_reference = pthread_create;
/* The constructor will never be executed because the module cannot be
loaded. */
static void __attribute__ ((constructor))
init (void)
{
puts ("tst-dlopenfailmod1 constructor executed");
_exit (1);
}
+29
View File
@@ -0,0 +1,29 @@
/* Module which depends on on a NODELETE module, and can be loaded.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <pthread.h>
#include <stdio.h>
/* Force linking against libpthread. */
void *pthread_create_reference = pthread_create;
static void __attribute__ ((constructor))
init (void)
{
puts ("info: tst-dlopenfailmod2.so constructor invoked");
}
+27
View File
@@ -0,0 +1,27 @@
/* Helper module for tst-initfinilazyfail: lazy binding failure in destructor.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
/* An undefined function. Calling it will cause a lazy binding
failure. */
void undefined_function (void);
static void __attribute__ ((destructor))
fini (void)
{
undefined_function ();
}
+84
View File
@@ -0,0 +1,84 @@
/* Test that lazy binding failures in constructors and destructors are fatal.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include <dlfcn.h>
#include <string.h>
#include <support/capture_subprocess.h>
#include <support/check.h>
#include <support/xdlfcn.h>
static void
test_constructor (void *closure)
{
void *handle = dlopen ("tst-initlazyfailmod.so", RTLD_LAZY);
if (handle == NULL)
FAIL_EXIT (2, "dlopen did not terminate the process: %s", dlerror ());
else
FAIL_EXIT (2, "dlopen did not terminate the process (%p)", handle);
}
static void
test_destructor (void *closure)
{
void *handle = xdlopen ("tst-finilazyfailmod.so", RTLD_LAZY);
int ret = dlclose (handle);
const char *message = dlerror ();
if (message != NULL)
FAIL_EXIT (2, "dlclose did not terminate the process: %d, %s",
ret, message);
else
FAIL_EXIT (2, "dlopen did not terminate the process: %d", ret);
}
static int
do_test (void)
{
{
struct support_capture_subprocess proc
= support_capture_subprocess (test_constructor, NULL);
support_capture_subprocess_check (&proc, "constructor", 127,
sc_allow_stderr);
printf ("info: constructor failure output: [[%s]]\n", proc.err.buffer);
TEST_VERIFY (strstr (proc.err.buffer,
"tst-initfinilazyfail: symbol lookup error: ")
!= NULL);
TEST_VERIFY (strstr (proc.err.buffer,
"tst-initlazyfailmod.so: undefined symbol:"
" undefined_function\n") != NULL);
support_capture_subprocess_free (&proc);
}
{
struct support_capture_subprocess proc
= support_capture_subprocess (test_destructor, NULL);
support_capture_subprocess_check (&proc, "destructor", 127,
sc_allow_stderr);
printf ("info: destructor failure output: [[%s]]\n", proc.err.buffer);
TEST_VERIFY (strstr (proc.err.buffer,
"tst-initfinilazyfail: symbol lookup error: ")
!= NULL);
TEST_VERIFY (strstr (proc.err.buffer,
"tst-finilazyfailmod.so: undefined symbol:"
" undefined_function\n") != NULL);
support_capture_subprocess_free (&proc);
}
return 0;
}
#include <support/test-driver.c>
+27
View File
@@ -0,0 +1,27 @@
/* Helper module for tst-initfinilazyfail: lazy binding failure in constructor.
Copyright (C) 2019 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
/* An undefined function. Calling it will cause a lazy binding
failure. */
void undefined_function (void);
static void __attribute__ ((constructor))
init (void)
{
undefined_function ();
}
+19
View File
@@ -79,6 +79,21 @@ struct r_search_path_struct
int malloced;
};
/* Type used by the l_nodelete member. */
enum link_map_nodelete
{
/* This link map can be deallocated. */
link_map_nodelete_inactive,
/* This link map cannot be deallocated. */
link_map_nodelete_active,
/* This link map cannot be deallocated after dlopen has succeded.
dlopen turns this into link_map_nodelete_active. dlclose treats
this intermediate state as link_map_nodelete_active. */
link_map_nodelete_pending,
};
/* Structure describing a loaded shared object. The `l_next' and `l_prev'
members form a chain of all the shared objects loaded at startup.
@@ -203,6 +218,10 @@ struct link_map
freed, ie. not allocated with
the dummy malloc in ld.so. */
/* Actually of type enum link_map_nodelete. Separate byte due to
concurrent access. Only valid for l_type == lt_loaded. */
unsigned char l_nodelete;
#include <link_map.h>
/* Collected information about own RPATH directories. */
+24 -4
View File
@@ -328,7 +328,14 @@ struct rtld_global
/* This is zero at program start to signal that the global scope map is
allocated by rtld. Later it keeps the size of the map. It might be
reset if in _dl_close if the last global object is removed. */
size_t _ns_global_scope_alloc;
unsigned int _ns_global_scope_alloc;
/* During dlopen, this is the number of objects that still need to
be added to the global scope map. It has to be taken into
account when resizing the map, for future map additions after
recursive dlopen calls from ELF constructors. */
unsigned int _ns_global_scope_pending_adds;
/* Search table for unique objects. */
struct unique_sym_table
{
@@ -860,7 +867,9 @@ libc_hidden_proto (_dl_catch_error)
/* Call OPERATE (ARGS). If no error occurs, set *EXCEPTION to zero.
Otherwise, store a copy of the raised exception in *EXCEPTION,
which has to be freed by _dl_exception_free. */
which has to be freed by _dl_exception_free. As a special case, if
EXCEPTION is null, call OPERATE (ARGS) with exception handling
disabled (so that exceptions are fatal). */
int _dl_catch_exception (struct dl_exception *exception,
void (*operate) (void *), void *args);
libc_hidden_proto (_dl_catch_exception)
@@ -914,6 +923,10 @@ enum
DL_LOOKUP_RETURN_NEWEST = 2,
/* Set if dl_lookup* called with GSCOPE lock held. */
DL_LOOKUP_GSCOPE_LOCK = 4,
/* Set if dl_lookup* is called from within dlopen, for non-lazy
binding. */
DL_LOOKUP_INSIDE_DLOPEN = 8,
};
/* Lookup versioned symbol. */
@@ -1131,8 +1144,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. */