32df52
commit d0f07f7df8d9758c838674b70144ac73bcbd1634
32df52
Author: Florian Weimer <fweimer@redhat.com>
32df52
Date:   Tue May 30 13:25:50 2023 +0200
32df52
32df52
    elf: Make more functions available for binding during dlclose (bug 30425)
32df52
    
32df52
    Previously, after destructors for a DSO have been invoked, ld.so refused
32df52
    to bind against that DSO in all cases.  Relax this restriction somewhat
32df52
    if the referencing object is itself a DSO that is being unloaded.  This
32df52
    assumes that the symbol reference is not going to be stored anywhere.
32df52
    
32df52
    The situation in the test case can arise fairly easily with C++ and
32df52
    objects that are built with different optimization levels and therefore
32df52
    define different functions with vague linkage.
32df52
    
32df52
    Reviewed-by: Carlos O'Donell <carlos@redhat.com>
32df52
32df52
Conflicts:
32df52
	elf/Makefile
32df52
	  (usual test differences, link test with -ldl)
32df52
32df52
diff --git a/elf/Makefile b/elf/Makefile
32df52
index 634c3113227d64a6..42dc878209b11d29 100644
32df52
--- a/elf/Makefile
32df52
+++ b/elf/Makefile
32df52
@@ -362,6 +362,7 @@ tests += \
32df52
   tst-big-note \
32df52
   tst-debug1 \
32df52
   tst-deep1 \
32df52
+  tst-dlclose-lazy \
32df52
   tst-dlmodcount \
32df52
   tst-dlmopen1 \
32df52
   tst-dlmopen3 \
32df52
@@ -709,6 +710,8 @@ modules-names = \
32df52
   tst-deep1mod2 \
32df52
   tst-deep1mod3 \
32df52
   tst-dlmopen1mod \
32df52
+  tst-dlclose-lazy-mod1 \
32df52
+  tst-dlclose-lazy-mod2 \
32df52
   tst-dlmopen-dlerror-mod \
32df52
   tst-dlmopen-gethostbyname-mod \
32df52
   tst-dlmopen-twice-mod1 \
32df52
@@ -2697,3 +2700,10 @@ $(objpfx)tst-dlmopen-twice: $(libdl)
32df52
 $(objpfx)tst-dlmopen-twice.out: \
32df52
   $(objpfx)tst-dlmopen-twice-mod1.so \
32df52
   $(objpfx)tst-dlmopen-twice-mod2.so
32df52
+
32df52
+LDFLAGS-tst-dlclose-lazy-mod1.so = -Wl,-z,lazy,--no-as-needed
32df52
+$(objpfx)tst-dlclose-lazy-mod1.so: $(objpfx)tst-dlclose-lazy-mod2.so
32df52
+$(objpfx)tst-dlclose-lazy: $(libdl)
32df52
+$(objpfx)tst-dlclose-lazy.out: \
32df52
+  $(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.so
32df52
+
32df52
diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c
32df52
index 47acd134600b44b5..9e8f14b8483f5eba 100644
32df52
--- a/elf/dl-lookup.c
32df52
+++ b/elf/dl-lookup.c
32df52
@@ -380,8 +380,25 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash,
32df52
       if ((type_class & ELF_RTYPE_CLASS_COPY) && map->l_type == lt_executable)
32df52
 	continue;
32df52
 
32df52
-      /* Do not look into objects which are going to be removed.  */
32df52
-      if (map->l_removed)
32df52
+      /* Do not look into objects which are going to be removed,
32df52
+	 except when the referencing object itself is being removed.
32df52
+
32df52
+	 The second part covers the situation when an object lazily
32df52
+	 binds to another object while running its destructor, but the
32df52
+	 destructor of the other object has already run, so that
32df52
+	 dlclose has set l_removed.  It may not always be obvious how
32df52
+	 to avoid such a scenario to programmers creating DSOs,
32df52
+	 particularly if C++ vague linkage is involved and triggers
32df52
+	 symbol interposition.
32df52
+
32df52
+	 Accepting these to-be-removed objects makes the lazy and
32df52
+	 BIND_NOW cases more similar.  (With BIND_NOW, the symbol is
32df52
+	 resolved early, before the destructor call, so the issue does
32df52
+	 not arise.).  Behavior matches the constructor scenario: the
32df52
+	 implementation allows binding to symbols of objects whose
32df52
+	 constructors have not run.  In fact, not doing this would be
32df52
+	 mostly incompatible with symbol interposition.  */
32df52
+      if (map->l_removed && !(undef_map != NULL && undef_map->l_removed))
32df52
 	continue;
32df52
 
32df52
       /* Print some debugging info if wanted.  */
32df52
diff --git a/elf/tst-dlclose-lazy-mod1.c b/elf/tst-dlclose-lazy-mod1.c
32df52
new file mode 100644
32df52
index 0000000000000000..8439dc1925cc8b41
32df52
--- /dev/null
32df52
+++ b/elf/tst-dlclose-lazy-mod1.c
32df52
@@ -0,0 +1,36 @@
32df52
+/* Lazy binding during dlclose.  Directly loaded module.
32df52
+   Copyright (C) 2023 Free Software Foundation, Inc.
32df52
+   This file is part of the GNU C Library.
32df52
+
32df52
+   The GNU C Library is free software; you can redistribute it and/or
32df52
+   modify it under the terms of the GNU Lesser General Public
32df52
+   License as published by the Free Software Foundation; either
32df52
+   version 2.1 of the License, or (at your option) any later version.
32df52
+
32df52
+   The GNU C Library is distributed in the hope that it will be useful,
32df52
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
32df52
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
32df52
+   Lesser General Public License for more details.
32df52
+
32df52
+   You should have received a copy of the GNU Lesser General Public
32df52
+   License along with the GNU C Library; if not, see
32df52
+   <https://www.gnu.org/licenses/>.  */
32df52
+
32df52
+/* This function is called from exported_function below.  It is only
32df52
+   defined in this module.  The weak attribute mimics how G++
32df52
+   implements vague linkage for C++.  */
32df52
+void __attribute__ ((weak))
32df52
+lazily_bound_exported_function (void)
32df52
+{
32df52
+}
32df52
+
32df52
+/* Called from tst-dlclose-lazy-mod2.so.  */
32df52
+void
32df52
+exported_function (int call_it)
32df52
+{
32df52
+  if (call_it)
32df52
+    /* Previous to the fix this would crash when called during dlclose
32df52
+       since symbols from the DSO were no longer available for binding
32df52
+       (bug 30425) after the DSO started being closed by dlclose.  */
32df52
+    lazily_bound_exported_function ();
32df52
+}
32df52
diff --git a/elf/tst-dlclose-lazy-mod2.c b/elf/tst-dlclose-lazy-mod2.c
32df52
new file mode 100644
32df52
index 0000000000000000..767f69ffdb23a685
32df52
--- /dev/null
32df52
+++ b/elf/tst-dlclose-lazy-mod2.c
32df52
@@ -0,0 +1,49 @@
32df52
+/* Lazy binding during dlclose.  Indirectly loaded module.
32df52
+   Copyright (C) 2023 Free Software Foundation, Inc.
32df52
+   This file is part of the GNU C Library.
32df52
+
32df52
+   The GNU C Library is free software; you can redistribute it and/or
32df52
+   modify it under the terms of the GNU Lesser General Public
32df52
+   License as published by the Free Software Foundation; either
32df52
+   version 2.1 of the License, or (at your option) any later version.
32df52
+
32df52
+   The GNU C Library is distributed in the hope that it will be useful,
32df52
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
32df52
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
32df52
+   Lesser General Public License for more details.
32df52
+
32df52
+   You should have received a copy of the GNU Lesser General Public
32df52
+   License along with the GNU C Library; if not, see
32df52
+   <https://www.gnu.org/licenses/>.  */
32df52
+
32df52
+#include <stdio.h>
32df52
+#include <stdlib.h>
32df52
+
32df52
+void
32df52
+exported_function (int ignored)
32df52
+{
32df52
+  /* This function is interposed from tst-dlclose-lazy-mod1.so and
32df52
+     thus never called.  */
32df52
+  abort ();
32df52
+}
32df52
+
32df52
+static void __attribute__ ((constructor))
32df52
+init (void)
32df52
+{
32df52
+  puts ("info: tst-dlclose-lazy-mod2.so constructor called");
32df52
+
32df52
+  /* Trigger lazy binding to the definition in
32df52
+     tst-dlclose-lazy-mod1.so, but not for
32df52
+     lazily_bound_exported_function in that module.  */
32df52
+  exported_function (0);
32df52
+}
32df52
+
32df52
+static void __attribute__ ((destructor))
32df52
+fini (void)
32df52
+{
32df52
+  puts ("info: tst-dlclose-lazy-mod2.so destructor called");
32df52
+
32df52
+  /* Trigger the lazily_bound_exported_function call in
32df52
+     exported_function in tst-dlclose-lazy-mod1.so.  */
32df52
+  exported_function (1);
32df52
+}
32df52
diff --git a/elf/tst-dlclose-lazy.c b/elf/tst-dlclose-lazy.c
32df52
new file mode 100644
32df52
index 0000000000000000..976a6bb6f64fa981
32df52
--- /dev/null
32df52
+++ b/elf/tst-dlclose-lazy.c
32df52
@@ -0,0 +1,47 @@
32df52
+/* Test lazy binding during dlclose (bug 30425).
32df52
+   Copyright (C) 2023 Free Software Foundation, Inc.
32df52
+   This file is part of the GNU C Library.
32df52
+
32df52
+   The GNU C Library is free software; you can redistribute it and/or
32df52
+   modify it under the terms of the GNU Lesser General Public
32df52
+   License as published by the Free Software Foundation; either
32df52
+   version 2.1 of the License, or (at your option) any later version.
32df52
+
32df52
+   The GNU C Library is distributed in the hope that it will be useful,
32df52
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
32df52
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
32df52
+   Lesser General Public License for more details.
32df52
+
32df52
+   You should have received a copy of the GNU Lesser General Public
32df52
+   License along with the GNU C Library; if not, see
32df52
+   <https://www.gnu.org/licenses/>.  */
32df52
+
32df52
+/* This test re-creates a situation that can arise naturally for C++
32df52
+   applications due to the use of vague linkage and differences in the
32df52
+   set of compiler-emitted functions.  A function in
32df52
+   tst-dlclose-lazy-mod1.so (exported_function) interposes a function
32df52
+   in tst-dlclose-lazy-mod2.so.  This function is called from the
32df52
+   destructor in tst-dlclose-lazy-mod2.so, after the destructor for
32df52
+   tst-dlclose-lazy-mod1.so has already completed.  Prior to the fix
32df52
+   for bug 30425, this would lead to a lazy binding failure in
32df52
+   tst-dlclose-lazy-mod1.so because dlclose had already marked the DSO
32df52
+   as unavailable for binding (by setting l_removed).  */
32df52
+
32df52
+#include <dlfcn.h>
32df52
+#include <support/xdlfcn.h>
32df52
+#include <support/check.h>
32df52
+
32df52
+int
32df52
+main (void)
32df52
+{
32df52
+  /* Load tst-dlclose-lazy-mod1.so, indirectly loading
32df52
+     tst-dlclose-lazy-mod2.so.  */
32df52
+  void *handle = xdlopen ("tst-dlclose-lazy-mod1.so", RTLD_GLOBAL | RTLD_LAZY);
32df52
+
32df52
+  /* Invoke the destructor of tst-dlclose-lazy-mod2.so, which calls
32df52
+     into tst-dlclose-lazy-mod1.so after its destructor has been
32df52
+     called.  */
32df52
+  xdlclose (handle);
32df52
+
32df52
+  return 0;
32df52
+}