This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[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


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]