32df52
commit 849274d48fc59bfa6db3c713c8ced8026b20f3b7
32df52
Author: Florian Weimer <fweimer@redhat.com>
32df52
Date:   Thu Nov 16 19:55:35 2023 +0100
32df52
32df52
    elf: Fix force_first handling in dlclose (bug 30981)
32df52
    
32df52
    The force_first parameter was ineffective because the dlclose'd
32df52
    object was not necessarily the first in the maps array.  Also
32df52
    enable force_first handling unconditionally, regardless of namespace.
32df52
    The initial object in a namespace should be destructed first, too.
32df52
    
32df52
    The _dl_sort_maps_dfs function had early returns for relocation
32df52
    dependency processing which broke force_first handling, too, and
32df52
    this is fixed in this change as well.
32df52
    
32df52
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
32df52
32df52
diff --git a/elf/dl-close.c b/elf/dl-close.c
32df52
index 66524b6708c59f29..8107c2d5f6ad2bc6 100644
32df52
--- a/elf/dl-close.c
32df52
+++ b/elf/dl-close.c
32df52
@@ -182,6 +182,16 @@ _dl_close_worker (struct link_map *map, bool force)
32df52
     }
32df52
   assert (idx == nloaded);
32df52
 
32df52
+  /* Put the dlclose'd map first, so that its destructor runs first.
32df52
+     The map variable is NULL after a retry.  */
32df52
+  if (map != NULL)
32df52
+    {
32df52
+      maps[map->l_idx] = maps[0];
32df52
+      maps[map->l_idx]->l_idx = map->l_idx;
32df52
+      maps[0] = map;
32df52
+      maps[0]->l_idx = 0;
32df52
+    }
32df52
+
32df52
   /* Keep track of the lowest index link map we have covered already.  */
32df52
   int done_index = -1;
32df52
   while (++done_index < nloaded)
32df52
@@ -255,9 +265,10 @@ _dl_close_worker (struct link_map *map, bool force)
32df52
 	  }
32df52
     }
32df52
 
32df52
-  /* Sort the entries.  We can skip looking for the binary itself which is
32df52
-     at the front of the search list for the main namespace.  */
32df52
-  _dl_sort_maps (maps, nloaded, (nsid == LM_ID_BASE), true);
32df52
+  /* Sort the entries.  Unless retrying, the maps[0] object (the
32df52
+     original argument to dlclose) needs to remain first, so that its
32df52
+     destructor runs first.  */
32df52
+  _dl_sort_maps (maps, nloaded, /* force_first */ map != NULL, true);
32df52
 
32df52
   /* Call all termination functions at once.  */
32df52
   bool unload_any = false;
32df52
@@ -768,7 +779,11 @@ _dl_close_worker (struct link_map *map, bool force)
32df52
   /* Recheck if we need to retry, release the lock.  */
32df52
  out:
32df52
   if (dl_close_state == rerun)
32df52
-    goto retry;
32df52
+    {
32df52
+      /* The map may have been deallocated.  */
32df52
+      map = NULL;
32df52
+      goto retry;
32df52
+    }
32df52
 
32df52
   dl_close_state = not_pending;
32df52
 }
32df52
diff --git a/elf/dl-sort-maps.c b/elf/dl-sort-maps.c
32df52
index aeb79b40b45054c0..c17ac325eca658ef 100644
32df52
--- a/elf/dl-sort-maps.c
32df52
+++ b/elf/dl-sort-maps.c
32df52
@@ -260,13 +260,12 @@ _dl_sort_maps_dfs (struct link_map **maps, unsigned int nmaps,
32df52
 	     The below memcpy is not needed in the do_reldeps case here,
32df52
 	     since we wrote back to maps[] during DFS traversal.  */
32df52
 	  if (maps_head == maps)
32df52
-	    return;
32df52
+	    break;
32df52
 	}
32df52
       assert (maps_head == maps);
32df52
-      return;
32df52
     }
32df52
-
32df52
-  memcpy (maps, rpo, sizeof (struct link_map *) * nmaps);
32df52
+  else
32df52
+    memcpy (maps, rpo, sizeof (struct link_map *) * nmaps);
32df52
 
32df52
   /* Skipping the first object at maps[0] is not valid in general,
32df52
      since traversing along object dependency-links may "find" that
32df52
diff --git a/elf/dso-sort-tests-1.def b/elf/dso-sort-tests-1.def
32df52
index 4bf9052db16fb352..cf6453e9eb85ac65 100644
32df52
--- a/elf/dso-sort-tests-1.def
32df52
+++ b/elf/dso-sort-tests-1.def
32df52
@@ -56,14 +56,16 @@ output: b>a>{}
32df52
 # relocation(dynamic) dependencies. While this is technically unspecified, the
32df52
 # presumed reasonable practical behavior is for the destructor order to respect
32df52
 # the static DT_NEEDED links (here this means the a->b->c->d order).
32df52
-# The older dynamic_sort=1 algorithm does not achieve this, while the DFS-based
32df52
-# dynamic_sort=2 algorithm does, although it is still arguable whether going
32df52
-# beyond spec to do this is the right thing to do.
32df52
+# The older dynamic_sort=1 algorithm originally did not achieve this,
32df52
+# but this was a bug in the way _dl_sort_maps was called from _dl_close_worker,
32df52
+# effectively disabling proper force_first handling.
32df52
+# The new dynamic_sort=2 algorithm shows the effect of the simpler force_first
32df52
+# handling: the a object is simply moved to the front.
32df52
 # The below expected outputs are what the two algorithms currently produce
32df52
 # respectively, for regression testing purposes.
32df52
 tst-bz15311: {+a;+e;+f;+g;+d;%d;-d;-g;-f;-e;-a};a->b->c->d;d=>[ba];c=>a;b=>e=>a;c=>f=>b;d=>g=>c
32df52
-output(glibc.rtld.dynamic_sort=1): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[
32df52
-output(glibc.rtld.dynamic_sort=2): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[
32df52
+output(glibc.rtld.dynamic_sort=1): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[
32df52
+output(glibc.rtld.dynamic_sort=2): {+a[d>c>b>a>];+e[e>];+f[f>];+g[g>];+d[];%d(b(e(a()))a()g(c(a()f(b(e(a()))))));-d[];-g[];-f[];-e[];-a[
32df52
 
32df52
 # Test that even in the presence of dependency loops involving dlopen'ed
32df52
 # object, that object is initialized last (and not unloaded prematurely).