This is the mail archive of the
libc-alpha@sourceware.org
mailing list for the glibc project.
[PATCH] ld.so: Fix dlclose() removing required local scope elementsof NODELETE linkmaps
- From: Petr Baudis <pasky at ucw dot cz>
- To: libc-alpha at sourceware dot org
- Date: Fri, 13 Apr 2012 18:42:25 +0200
- Subject: [PATCH] ld.so: Fix dlclose() removing required local scope elementsof NODELETE linkmaps
http://sourceware.org/bugzilla/show_bug.cgi?id=12561
(Waiting for review for around a year now.)
In case a library is opened with RTLD_LOCAL, dlclose()ing that library
will remove the local scope from all subsequently loaded libraries
unconditionally, even though such a library is marked as RTLD_NODELETE.
This causes subsequent lookups within that library to fail if the
library depends on other libraries than those already loaded within
the global scope.
This has been exposed in a real-world case where libproxy opens
a KDE4 plugin with RTLD_LOCAL, the plugin depends on libkde4_core
and libkde4_core is marked as NODELETE due to having a STB_GNU_UNIQ
symbol; the plugin is dlclose()d later but ld.so raises a fatal
error when libkde4_core global destructor is called (it depends
on libqt4, but libqt4 has been in the plugin's local scope only
and is gone now).
Testcase (by Michael Matz):
http://sourceware.org/bugzilla/attachment.cgi?id=5749
Ok to commit?
2012-04-13 Petr Baudis <pasky@suse.cz>
* elf/dl-close.c (_dl_close_worker): Factor out mark_used macro.
Call it also for owners of scopes of DF_1_NODELETE linkmaps.
* elf/nodel2mod1.c (quux): Introduce another helper symbol.
* elf/nodel2mod2.c (nodelete_callback): Introduce callback symbol.
* elf/nodelete2.c: Check if scope of DF_1_NODELETE dependency
remains unscathed.
diff -ru elf~/dl-close.c elf/dl-close.c
--- elf~/dl-close.c 2011-02-04 00:35:03.000000000 +0100
+++ elf/dl-close.c 2011-02-22 02:16:12.367883000 +0100
@@ -180,24 +186,28 @@
/* Signal the object is still needed. */
l->l_idx = IDX_STILL_USED;
+#define mark_used(dmap) \
+ do { \
+ if ((dmap)->l_idx != IDX_STILL_USED) \
+ { \
+ assert ((dmap)->l_idx >= 0 && (dmap)->l_idx < nloaded); \
+ \
+ if (!used[(dmap)->l_idx]) \
+ { \
+ used[(dmap)->l_idx] = 1; \
+ if ((dmap)->l_idx - 1 < done_index) \
+ done_index = (dmap)->l_idx - 1; \
+ } \
+ } \
+ } while (0)
+
/* Mark all dependencies as used. */
if (l->l_initfini != NULL)
{
struct link_map **lp = &l->l_initfini[1];
while (*lp != NULL)
{
- if ((*lp)->l_idx != IDX_STILL_USED)
- {
- assert ((*lp)->l_idx >= 0 && (*lp)->l_idx < nloaded);
-
- if (!used[(*lp)->l_idx])
- {
- used[(*lp)->l_idx] = 1;
- if ((*lp)->l_idx - 1 < done_index)
- done_index = (*lp)->l_idx - 1;
- }
- }
-
+ mark_used(*lp);
++lp;
}
}
@@ -206,19 +216,25 @@
for (unsigned int j = 0; j < l->l_reldeps->act; ++j)
{
struct link_map *jmap = l->l_reldeps->list[j];
-
- if (jmap->l_idx != IDX_STILL_USED)
- {
- assert (jmap->l_idx >= 0 && jmap->l_idx < nloaded);
-
- if (!used[jmap->l_idx])
- {
- used[jmap->l_idx] = 1;
- if (jmap->l_idx - 1 < done_index)
- done_index = jmap->l_idx - 1;
- }
- }
+ mark_used(jmap);
}
+ /* And the same for owners of our scopes; normally, our last
+ scope provider would render us unused, but this can be
+ prevented by the NODELETE flag. */
+ if (__builtin_expect(l->l_type == lt_loaded
+ && (l->l_flags_1 & DF_1_NODELETE), 0))
+ for (size_t cnt = 0; l->l_scope[cnt] != NULL; ++cnt)
+ /* This relies on l_scope[] entries being always set either
+ to its own l_symbolic_searchlist address, or some map's
+ l_searchlist address. */
+ if (l->l_scope[cnt] != &l->l_symbolic_searchlist)
+ {
+ struct link_map *ls = (struct link_map *)
+ ((char *) l->l_scope[cnt]
+ - offsetof (struct link_map, l_searchlist));
+ assert (ls->l_ns == nsid);
+ mark_used(ls);
+ }
}
/* Sort the entries. */
diff --git a/elf/nodel2mod1.c b/elf/nodel2mod1.c
index acddc4c..3abfabe 100644
--- a/elf/nodel2mod1.c
+++ b/elf/nodel2mod1.c
@@ -17,3 +17,7 @@ void
baz (void)
{
}
+void
+quux (void)
+{
+}
diff --git a/elf/nodel2mod2.c b/elf/nodel2mod2.c
index d002024..e9fd6ca 100644
--- a/elf/nodel2mod2.c
+++ b/elf/nodel2mod2.c
@@ -5,3 +5,9 @@ xxx (void)
extern void baz (void);
baz ();
}
+void
+nodelete_callback (void)
+{
+ extern void quux (void);
+ quux ();
+}
diff --git a/elf/nodelete2.c b/elf/nodelete2.c
index b3d7e31..d5298f6 100644
--- a/elf/nodelete2.c
+++ b/elf/nodelete2.c
@@ -6,11 +6,15 @@ int
main (void)
{
void *handle = dlopen ("nodel2mod3.so", RTLD_LAZY);
- if (handle == NULL)
+ void (*callback)(void);
+ if (handle != NULL)
+ callback = dlsym (handle, "nodelete_callback");
+ if (handle == NULL || callback == NULL)
{
printf ("%s\n", dlerror ());
exit (1);
}
dlclose (handle);
+ callback (); /* If this breaks, we have broken nodel2mod2's scope. */
exit (1);
}
--
Petr "Pasky" Baudis
Smart data structures and dumb code works a lot better
than the other way around. -- Eric S. Raymond