about summary refs log tree commit diff
path: root/elf/tst-dlopen-nodelete-reloc.c
diff options
context:
space:
mode:
authorFlorian Weimer <fweimer@redhat.com>2019-12-13 10:18:24 +0100
committerFlorian Weimer <fweimer@redhat.com>2019-12-13 10:18:24 +0100
commit365624e2d2a342cdb693b4cc35d2312169959e28 (patch)
tree4a17435022fd7b0c03690c7ad3444b0d3c030ced /elf/tst-dlopen-nodelete-reloc.c
parent186e119bbd4a10895429ffe405ae96dc5c5634b8 (diff)
downloadglibc-365624e2d2a342cdb693b4cc35d2312169959e28.tar.gz
glibc-365624e2d2a342cdb693b4cc35d2312169959e28.tar.xz
glibc-365624e2d2a342cdb693b4cc35d2312169959e28.zip
dlopen: Fix issues related to NODELETE handling and relocations
The assumption behind the assert in activate_nodelete was wrong:

Inconsistency detected by ld.so: dl-open.c: 459: activate_nodelete:
Assertion `!imap->l_init_called || imap->l_type != lt_loaded' failed! (edit)

It can happen that an already-loaded object that is in the local
scope is promoted to NODELETE status, via binding to a unique
symbol.

Similarly, it is possible that such NODELETE promotion occurs to
an already-loaded object from the global scope.  This is why the
loop in activate_nodelete has to cover all objects in the namespace
of the new object.

In do_lookup_unique, it could happen that the NODELETE status of
an already-loaded object was overwritten with a pending NODELETE
status.  As a result, if dlopen fails, this could cause a loss of
the NODELETE status of the affected object, eventually resulting
in an incorrect unload.

Fixes commit f63b73814f74032c0e5d0a83300e3d864ef905e5 ("Remove all
loaded objects if dlopen fails, ignoring NODELETE [BZ #20839]").
Diffstat (limited to 'elf/tst-dlopen-nodelete-reloc.c')
-rw-r--r--elf/tst-dlopen-nodelete-reloc.c179
1 files changed, 179 insertions, 0 deletions
diff --git a/elf/tst-dlopen-nodelete-reloc.c b/elf/tst-dlopen-nodelete-reloc.c
new file mode 100644
index 0000000000..291ac9eb83
--- /dev/null
+++ b/elf/tst-dlopen-nodelete-reloc.c
@@ -0,0 +1,179 @@
+/* Test interactions of dlopen, NODELETE, and relocations.
+   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/>.  */
+
+/* This test exercises NODELETE propagation due to data relocations
+   and unique symbols, and the interaction with already-loaded
+   objects.  Some test objects are written in C++, to produce unique
+   symbol definitions.
+
+   First test: Global scope variant, data relocation as the NODELETE
+   trigger.  mod1 is loaded first with a separate dlopen call.
+
+      mod2 ---(may_finalize_mod1 relocation dependency)---> mod1
+    (NODELETE)                                   (marked as NODELETE)
+
+   Second test: Local scope variant, data relocation.  mod3 is loaded
+   first, then mod5.
+
+      mod5 ---(DT_NEEDED)--->  mod4  ---(DT_NEEDED)---> mod3
+    (NODELETE)           (not NODELETE)                  ^
+        \                                               / (marked as
+         `--(may_finalize_mod3 relocation dependency)--/   NODELETE)
+
+   Third test: Shared local scope with unique symbol.  mod6 is loaded
+   first, then mod7.  No explicit dependencies between the two
+   objects, so first object has to be opened with RTLD_GLOBAL.
+
+      mod7 ---(unique symbol)---> mod6
+                          (marked as NODELETE)
+
+   Forth test: Non-shared scopes with unique symbol.  mod8 and mod10
+   are loaded from the main program.  mod8 loads mod9 from an ELF
+   constructor, mod10 loads mod11.  There are no DT_NEEDED
+   dependencies.  mod9 is promoted to the global scope form the main
+   program.  The unique symbol dependency is:
+
+      mod9 ---(unique symbol)---> mod11
+                          (marked as NODELETE)
+
+   Fifth test: Shared local scope with unique symbol, like test 3, but
+   this time, there is also a DT_NEEDED dependency (so no RTLD_GLOBAL
+   needed):
+
+                 DT_NEEDED
+      mod13 ---(unique symbol)---> mod12
+                          (marked as NODELETE)
+
+   Sixth test: NODELETE status is retained after relocation failure
+   with unique symbol dependency.  The object graph ensures that the
+   unique symbol binding is processed before the dlopen failure.
+
+                                        DT_NEEDED
+     mod17  --(DT_NEEDED)--> mod15 --(unique symbol)--> mod14
+       \                       ^                  (RTLD_NODELETE)
+        \                 (DT_NEEDED)
+         \                     |
+          `---(DT_NEEDED)--> mod16
+                       (fails to relocate)
+
+   mod14 is loaded first, and the loading mod17 is attempted.
+   mod14 must remain NODELETE after opening mod17 failed.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdbool.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+  /* First case: global scope, regular data symbol.  Open the object
+     which is not NODELETE initially.  */
+  void *mod1 = xdlopen ("tst-dlopen-nodelete-reloc-mod1.so",
+                        RTLD_NOW | RTLD_GLOBAL);
+  /* This is used to indicate that the ELF destructor may be
+     called.  */
+  bool *may_finalize_mod1 = xdlsym (mod1, "may_finalize_mod1");
+  /* Open the NODELETE object.  */
+  void *mod2 = xdlopen ("tst-dlopen-nodelete-reloc-mod2.so", RTLD_NOW);
+  /* This has no effect because the DSO is directly marked as
+     NODELETE.  */
+  xdlclose (mod2);
+  /* This has no effect because the DSO has been indirectly marked as
+     NODELETE due to a relocation dependency.  */
+  xdlclose (mod1);
+
+  /* Second case: local scope, regular data symbol.  Open the object
+     which is not NODELETE initially.  */
+  void *mod3 = xdlopen ("tst-dlopen-nodelete-reloc-mod3.so", RTLD_NOW);
+  bool *may_finalize_mod3 = xdlsym (mod3, "may_finalize_mod3");
+  /* Open the NODELETE object.  */
+  void *mod5 = xdlopen ("tst-dlopen-nodelete-reloc-mod5.so", RTLD_NOW);
+  /* Again those have no effect because of NODELETE.  */
+  xdlclose (mod5);
+  xdlclose (mod3);
+
+  /* Third case: Unique symbol.  */
+  void *mod6 = xdlopen ("tst-dlopen-nodelete-reloc-mod6.so",
+                        RTLD_NOW | RTLD_GLOBAL);
+  bool *may_finalize_mod6 = xdlsym (mod6, "may_finalize_mod6");
+  void *mod7 = xdlopen ("tst-dlopen-nodelete-reloc-mod7.so", RTLD_NOW);
+  bool *may_finalize_mod7 = xdlsym (mod7, "may_finalize_mod7");
+  /* This should not have any effect because of the unique symbol and
+     the resulting NODELETE status.  */
+  xdlclose (mod6);
+  /* mod7 is not NODELETE and can be closed.  */
+  *may_finalize_mod7 = true;
+  xdlclose (mod7);
+
+  /* Fourth case: Unique symbol, indirect loading.  */
+  void *mod8 = xdlopen ("tst-dlopen-nodelete-reloc-mod8.so", RTLD_NOW);
+  /* Also promote to global scope.  */
+  void *mod9 = xdlopen ("tst-dlopen-nodelete-reloc-mod9.so",
+                        RTLD_NOW | RTLD_NOLOAD | RTLD_GLOBAL);
+  bool *may_finalize_mod9 = xdlsym (mod9, "may_finalize_mod9");
+  xdlclose (mod9);              /* Drop mod9 reference.  */
+  void *mod10 = xdlopen ("tst-dlopen-nodelete-reloc-mod10.so", RTLD_NOW);
+  void *mod11 = xdlopen ("tst-dlopen-nodelete-reloc-mod11.so",
+                        RTLD_NOW | RTLD_NOLOAD);
+  bool *may_finalize_mod11 = xdlsym (mod11, "may_finalize_mod11");
+  xdlclose (mod11);              /* Drop mod11 reference.  */
+  /* mod11 is not NODELETE and can be closed.  */
+  *may_finalize_mod11 = true;
+  /* Trigger closing of mod11, too.  */
+  xdlclose (mod10);
+  /* Does not trigger closing of mod9.  */
+  xdlclose (mod8);
+
+  /* Fifth case: Unique symbol, with DT_NEEDED dependency.  */
+  void *mod12 = xdlopen ("tst-dlopen-nodelete-reloc-mod12.so", RTLD_NOW);
+  bool *may_finalize_mod12 = xdlsym (mod12, "may_finalize_mod12");
+  void *mod13 = xdlopen ("tst-dlopen-nodelete-reloc-mod13.so", RTLD_NOW);
+  bool *may_finalize_mod13 = xdlsym (mod13, "may_finalize_mod13");
+  /* This should not have any effect because of the unique symbol. */
+  xdlclose (mod12);
+  /* mod13 is not NODELETE and can be closed.  */
+  *may_finalize_mod13 = true;
+  xdlclose (mod13);
+
+  /* Sixth case: Unique symbol binding must not cause loss of NODELETE
+     status.  */
+  void *mod14 = xdlopen ("tst-dlopen-nodelete-reloc-mod14.so",
+                         RTLD_NOW | RTLD_NODELETE);
+  bool *may_finalize_mod14 = xdlsym (mod14, "may_finalize_mod14");
+  TEST_VERIFY (dlopen ("tst-dlopen-nodelete-reloc-mod17.so", RTLD_NOW)
+               == NULL);
+  const char *message = dlerror ();
+  printf ("info: test 6 message: %s\n", message);
+  /* This must not close the object, it must still be NODELETE.  */
+  xdlclose (mod14);
+  xdlopen ("tst-dlopen-nodelete-reloc-mod14.so", RTLD_NOW | RTLD_NOLOAD);
+
+  /* Prepare for process exit.  Destructors for NODELETE objects will
+     be invoked.  */
+  *may_finalize_mod1 = true;
+  *may_finalize_mod3 = true;
+  *may_finalize_mod6 = true;
+  *may_finalize_mod9 = true;
+  *may_finalize_mod12 = true;
+  *may_finalize_mod14 = true;
+  return 0;
+}
+
+#include <support/test-driver.c>