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.

Note that the _dl_catch_exception in _dl_open is just an inner catch,
to roll back some state locally.  If called from dlopen, there is
still an outer catch, which is why calling _dl_init via call_dl_init
and a no-exception is required and cannot be avoiding by moving the
_dl_init call directly into _dl_open.

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

Change-Id: I6b1addfe2e30f50a1781595f046f44173db9491a
This commit is contained in:
Florian Weimer 2019-11-27 16:20:47 +01:00
parent 446997ff14
commit 79e0cd7b3c
7 changed files with 216 additions and 21 deletions

6
NEWS
View File

@ -93,6 +93,12 @@ Deprecated and removed features, and other changes affecting compatibility:
are no longer supported. For v8 only implementations with native CAS
instruction are still supported (such as LEON).
* If a lazy binding failure happens during dlopen, during the execution of
an ELF constructor, the process is now terminated. Previously, the
dynamic loader would return NULL from dlopen, with the lazy binding error
captured in a dlerror message. In general, this is unsafe because
resetting the stack in an arbitrary function call is not possible.
Changes to build and runtime requirements:
[Add changes to build and runtime requirements here]

View File

@ -199,7 +199,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-auditmany
tst-dlopen-self tst-auditmany tst-initfinilazyfail
# reldep9
tests-internal += loadtest unload unload2 circleload1 \
neededtest neededtest2 neededtest3 neededtest4 \
@ -290,7 +290,8 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
tst-sonamemove-runmod1 tst-sonamemove-runmod2 \
tst-auditmanymod1 tst-auditmanymod2 tst-auditmanymod3 \
tst-auditmanymod4 tst-auditmanymod5 tst-auditmanymod6 \
tst-auditmanymod7 tst-auditmanymod8 tst-auditmanymod9
tst-auditmanymod7 tst-auditmanymod8 tst-auditmanymod9 \
tst-initlazyfailmod tst-finilazyfailmod
# 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%,\
@ -1586,3 +1587,13 @@ $(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

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)
@ -267,7 +291,8 @@ _dl_close_worker (struct link_map *map, bool force)
&& (imap->l_flags_1 & DF_1_NODELETE) == 0);
/* 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 +301,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

View File

@ -177,6 +177,23 @@ _dl_find_dso_for_object (const ElfW(Addr) addr)
}
rtld_hidden_def (_dl_find_dso_for_object);
/* 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)
{
@ -509,8 +526,19 @@ 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)

27
elf/tst-finilazyfailmod.c Normal file
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 ();
}

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
elf/tst-initlazyfailmod.c Normal file
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 ();
}