]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
Lazy binding failures during dlopen/dlclose must be fatal [BZ #24304]
authorFlorian Weimer <fweimer@redhat.com>
Thu, 17 Oct 2019 09:20:39 +0000 (11:20 +0200)
committerFlorian Weimer <fweimer@redhat.com>
Thu, 17 Oct 2019 09:20:39 +0000 (11:20 +0200)
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.

elf/Makefile
elf/dl-close.c
elf/dl-error-skeleton.c
elf/dl-open.c
elf/tst-finilazyfailmod.c [new file with mode: 0644]
elf/tst-initfinilazyfail.c [new file with mode: 0644]
elf/tst-initlazyfailmod.c [new file with mode: 0644]
sysdeps/generic/ldsodefs.h

index 5e4cdb494fca6bd1148b3982af91d36b7b56795d..a09b6508b3ced31fe84e726e20270a5b9c802513 100644 (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
 #       reldep9
 tests-internal += loadtest unload unload2 circleload1 \
         neededtest neededtest2 neededtest3 neededtest4 \
@@ -281,7 +281,8 @@ 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
 # 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 +1570,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
index de91c28f728d0c0f8c0895053803dc4d5a6045d4..2e38943fb32ae7deb7775125d44bb790234ea003 100644 (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
index a261af68b6670bb0ef49774f55de9ebec6e6bc49..7caf28f0fdd0bd2786ec976cfb57a51ac2bcc242 100644 (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.  */
 
index a9fd4cbceddc08ae8849a5ebebf72c5c1b255d4b..e6151a0665d38cd4fa372ef4eec2773f96615657 100644 (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)
 {
@@ -506,8 +523,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)
diff --git a/elf/tst-finilazyfailmod.c b/elf/tst-finilazyfailmod.c
new file mode 100644 (file)
index 0000000..2670bd1
--- /dev/null
@@ -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 ();
+}
diff --git a/elf/tst-initfinilazyfail.c b/elf/tst-initfinilazyfail.c
new file mode 100644 (file)
index 0000000..9b4a3d0
--- /dev/null
@@ -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>
diff --git a/elf/tst-initlazyfailmod.c b/elf/tst-initlazyfailmod.c
new file mode 100644 (file)
index 0000000..36348b5
--- /dev/null
@@ -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 ();
+}
index f3ba13ee68792fd519337961e6b563e7d887d0b6..07789022e08a723e86a4e4fd7e763f54b59a7972 100644 (file)
@@ -860,7 +860,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)