File dlclose-assert-init-called.patch of Package glibc.6721
2016-12-24 Carlos O'Donell <carlos@redhat.com>
[BZ #11941]
* elf/dl-close.c (_dl_close): Take dl_load_lock to examine map.
Remove assert (map->l_init_called); if DF_1_NODELETE is set.
* elf/Makefile [ifeq (yes,$(build-shared))] (tests): Add
tst-nodelete-dlclose.
(modules-names): Add tst-nodelete-dlclose-dso and
tst-nodelete-dlclose-plugin.
($(objpfx)tst-nodelete-dlclose-dso.so): Define.
($(objpfx)tst-nodelete-dlclose-plugin.so): Define.
($(objpfx)tst-nodelete-dlclose): Define.
($(objpfx)tst-nodelete-dlclose.out): Define.
Index: glibc-2.22/elf/Makefile
===================================================================
--- glibc-2.22.orig/elf/Makefile
+++ glibc-2.22/elf/Makefile
@@ -148,7 +148,8 @@ tests += loadtest restest1 preloadtest l
tst-unique1 tst-unique2 $(if $(CXX),tst-unique3 tst-unique4 \
tst-nodelete) \
tst-initorder tst-initorder2 tst-relsort1 tst-null-argv \
- tst-ptrguard1 tst-tlsalign tst-tlsalign-extern tst-nodelete-opened
+ tst-ptrguard1 tst-tlsalign tst-tlsalign-extern tst-nodelete-opened \
+ tst-nodelete-dlclose
# reldep9
ifeq ($(build-hardcoded-path-in-tests),yes)
tests += tst-dlopen-aout
@@ -218,7 +219,8 @@ modules-names = testobj1 testobj2 testob
tst-initorder2d \
tst-relsort1mod1 tst-relsort1mod2 tst-array2dep \
tst-array5dep tst-null-argv-lib \
- tst-tlsalign-lib tst-nodelete-opened-lib
+ tst-tlsalign-lib tst-nodelete-opened-lib \
+ tst-nodelete-dlclose-dso tst-nodelete-dlclose-plugin
ifeq (yes,$(have-protected-data))
modules-names += tst-protected1moda tst-protected1modb
tests += tst-protected1a tst-protected1b
@@ -1222,3 +1224,12 @@ $(objpfx)tst-prelink-cmp.out: tst-prelin
$(objpfx)tst-prelink-conflict.out
cmp $^ > $@; \
$(evaluate-test)
+
+# The application depends on the DSO, and the DSO loads the plugin.
+# The plugin also depends on the DSO. This creates the circular
+# dependency via dlopen that we're testing to make sure works.
+$(objpfx)tst-nodelete-dlclose-dso.so: $(libdl)
+$(objpfx)tst-nodelete-dlclose-plugin.so: $(objpfx)tst-nodelete-dlclose-dso.so
+$(objpfx)tst-nodelete-dlclose: $(objpfx)tst-nodelete-dlclose-dso.so
+$(objpfx)tst-nodelete-dlclose.out: $(objpfx)tst-nodelete-dlclose-dso.so \
+ $(objpfx)tst-nodelete-dlclose-plugin.so
Index: glibc-2.22/elf/dl-close.c
===================================================================
--- glibc-2.22.orig/elf/dl-close.c
+++ glibc-2.22/elf/dl-close.c
@@ -804,19 +804,37 @@ _dl_close (void *_map)
{
struct link_map *map = _map;
- /* First see whether we can remove the object at all. */
+ /* We must take the lock to examine the contents of map and avoid
+ concurrent dlopens. */
+ __rtld_lock_lock_recursive (GL(dl_load_lock));
+
+ /* At this point we are guaranteed nobody else is touching the list of
+ loaded maps, but a concurrent dlclose might have freed our 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))
{
- assert (map->l_init_called);
/* Nope. Do nothing. */
+ __rtld_lock_unlock_recursive (GL(dl_load_lock));
return;
}
+ /* At present this is an unreliable check except in the case where the
+ caller has recursively called dlclose and we are sure the link map
+ has not been freed. In a non-recursive dlclose the map itself
+ might have been freed and this access is potentially a data race
+ with whatever other use this memory might have now, or worse we
+ might silently corrupt memory if it looks enough like a link map.
+ POSIX has language in dlclose that appears to guarantee that this
+ should be a detectable case and given that dlclose should be threadsafe
+ we need this to be a reliable detection.
+ This is bug 20990. */
if (__builtin_expect (map->l_direct_opencount, 1) == 0)
- GLRO(dl_signal_error) (0, map->l_name, NULL, N_("shared object not open"));
-
- /* Acquire the lock. */
- __rtld_lock_lock_recursive (GL(dl_load_lock));
+ {
+ __rtld_lock_unlock_recursive (GL(dl_load_lock));
+ _dl_signal_error (0, map->l_name, NULL, N_("shared object not open"));
+ }
_dl_close_worker (map, false);
Index: glibc-2.22/elf/tst-nodelete-dlclose-dso.c
===================================================================
--- /dev/null
+++ glibc-2.22/elf/tst-nodelete-dlclose-dso.c
@@ -0,0 +1,90 @@
+/* Bug 11941: Improper assert map->l_init_called in dlclose.
+ Copyright (C) 2016 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
+ <http://www.gnu.org/licenses/>. */
+
+/* This is the primary DSO that is loaded by the appliation. This DSO
+ then loads a plugin with RTLD_NODELETE. This plugin depends on this
+ DSO. This dependency chain means that at application shutdown the
+ plugin will be destructed first. Thus by the time this DSO is
+ destructed we will be calling dlclose on an object that has already
+ been destructed. It is allowed to call dlclose in this way and
+ should not assert. */
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+
+/* Plugin to load. */
+static void *plugin_lib = NULL;
+/* Plugin function. */
+static void (*plugin_func) (void);
+#define LIB_PLUGIN "tst-nodelete-dlclose-plugin.so"
+
+/* This function is never called but the plugin references it.
+ We do this to avoid any future --as-needed from removing the
+ plugin's DT_NEEDED on this DSO (required for the test). */
+void
+primary_reference (void)
+{
+ printf ("INFO: Called primary_reference function.\n");
+}
+
+void
+primary (void)
+{
+ char *error;
+
+ plugin_lib = dlopen (LIB_PLUGIN, RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE);
+ if (plugin_lib == NULL)
+ {
+ printf ("ERROR: Unable to load plugin library.\n");
+ exit (EXIT_FAILURE);
+ }
+ dlerror ();
+
+ plugin_func = (void (*) (void)) dlsym (plugin_lib, "plugin_func");
+ error = dlerror ();
+ if (error != NULL)
+ {
+ printf ("ERROR: Unable to find symbol with error \"%s\".",
+ error);
+ exit (EXIT_FAILURE);
+ }
+
+ return;
+}
+
+__attribute__ ((destructor))
+static void
+primary_dtor (void)
+{
+ int ret;
+
+ printf ("INFO: Calling primary destructor.\n");
+
+ /* The destructor runs in the test driver also, which
+ hasn't called primary, in that case do nothing. */
+ if (plugin_lib == NULL)
+ return;
+
+ ret = dlclose (plugin_lib);
+ if (ret != 0)
+ {
+ printf ("ERROR: Calling dlclose failed with \"%s\"\n",
+ dlerror ());
+ exit (EXIT_FAILURE);
+ }
+}
Index: glibc-2.22/elf/tst-nodelete-dlclose-plugin.c
===================================================================
--- /dev/null
+++ glibc-2.22/elf/tst-nodelete-dlclose-plugin.c
@@ -0,0 +1,40 @@
+/* Bug 11941: Improper assert map->l_init_called in dlclose.
+ Copyright (C) 2016 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
+ <http://www.gnu.org/licenses/>. */
+
+/* This DSO simulates a plugin with a dependency on the
+ primary DSO loaded by the appliation. */
+#include <stdio.h>
+
+extern void primary_reference (void);
+
+void
+plugin_func (void)
+{
+ printf ("INFO: Calling plugin function.\n");
+ /* Need a reference to the DSO to ensure that a potential --as-needed
+ doesn't remove the DT_NEEDED entry which we rely upon to ensure
+ destruction ordering. */
+ primary_reference ();
+}
+
+__attribute__ ((destructor))
+static void
+plugin_dtor (void)
+{
+ printf ("INFO: Calling plugin destructor.\n");
+}
Index: glibc-2.22/elf/tst-nodelete-dlclose.c
===================================================================
--- /dev/null
+++ glibc-2.22/elf/tst-nodelete-dlclose.c
@@ -0,0 +1,36 @@
+/* Bug 11941: Improper assert map->l_init_called in dlclose.
+ Copyright (C) 2016 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
+ <http://www.gnu.org/licenses/>. */
+
+/* This simulates an application using the primary DSO which loads the
+ plugin DSO. */
+#include <stdio.h>
+#include <stdlib.h>
+
+extern void primary (void);
+
+static int
+do_test (void)
+{
+ printf ("INFO: Starting application.\n");
+ primary ();
+ printf ("INFO: Exiting application.\n");
+ return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"