1feee8
commit 1df71d32fe5f5905ffd5d100e5e9ca8ad6210891
1feee8
Author: Florian Weimer <fweimer@redhat.com>
1feee8
Date:   Tue Sep 20 11:00:42 2022 +0200
1feee8
1feee8
    elf: Implement force_first handling in _dl_sort_maps_dfs (bug 28937)
1feee8
    
1feee8
    The implementation in _dl_close_worker requires that the first
1feee8
    element of l_initfini is always this very map (“We are always the
1feee8
    zeroth entry, and since we don't include ourselves in the
1feee8
    dependency analysis start at 1.”).  Rather than fixing that
1feee8
    assumption, this commit adds an implementation of the force_first
1feee8
    argument to the new dependency sorting algorithm.  This also means
1feee8
    that the directly dlopen'ed shared object is always initialized last,
1feee8
    which is the least surprising behavior in the presence of cycles.
1feee8
    
1feee8
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
1feee8
1feee8
diff --git a/elf/dl-sort-maps.c b/elf/dl-sort-maps.c
1feee8
index 7c614bca5d8115c6..e8ef5e8b3588ab53 100644
1feee8
--- a/elf/dl-sort-maps.c
1feee8
+++ b/elf/dl-sort-maps.c
1feee8
@@ -182,8 +182,9 @@ dfs_traversal (struct link_map ***rpo, struct link_map *map,
1feee8
 
1feee8
 static void
1feee8
 _dl_sort_maps_dfs (struct link_map **maps, unsigned int nmaps,
1feee8
-		   bool force_first __attribute__ ((unused)), bool for_fini)
1feee8
+		   bool force_first, bool for_fini)
1feee8
 {
1feee8
+  struct link_map *first_map = maps[0];
1feee8
   for (int i = nmaps - 1; i >= 0; i--)
1feee8
     maps[i]->l_visited = 0;
1feee8
 
1feee8
@@ -208,14 +209,6 @@ _dl_sort_maps_dfs (struct link_map **maps, unsigned int nmaps,
1feee8
      Adjusting the order so that maps[0] is last traversed naturally avoids
1feee8
      this problem.
1feee8
 
1feee8
-     Further, the old "optimization" of skipping the main object at maps[0]
1feee8
-     from the call-site (i.e. _dl_sort_maps(maps+1,nmaps-1)) is in general
1feee8
-     no longer valid, since traversing along object dependency-links
1feee8
-     may "find" the main object even when it is not included in the initial
1feee8
-     order (e.g. a dlopen()'ed shared object can have circular dependencies
1feee8
-     linked back to itself). In such a case, traversing N-1 objects will
1feee8
-     create a N-object result, and raise problems.
1feee8
-
1feee8
      To summarize, just passing in the full list, and iterating from back
1feee8
      to front makes things much more straightforward.  */
1feee8
 
1feee8
@@ -274,6 +267,27 @@ _dl_sort_maps_dfs (struct link_map **maps, unsigned int nmaps,
1feee8
     }
1feee8
 
1feee8
   memcpy (maps, rpo, sizeof (struct link_map *) * nmaps);
1feee8
+
1feee8
+  /* Skipping the first object at maps[0] is not valid in general,
1feee8
+     since traversing along object dependency-links may "find" that
1feee8
+     first object even when it is not included in the initial order
1feee8
+     (e.g., a dlopen'ed shared object can have circular dependencies
1feee8
+     linked back to itself).  In such a case, traversing N-1 objects
1feee8
+     will create a N-object result, and raise problems.  Instead,
1feee8
+     force the object back into first place after sorting.  This naive
1feee8
+     approach may introduce further dependency ordering violations
1feee8
+     compared to rotating the cycle until the first map is again in
1feee8
+     the first position, but as there is a cycle, at least one
1feee8
+     violation is already present.  */
1feee8
+  if (force_first && maps[0] != first_map)
1feee8
+    {
1feee8
+      int i;
1feee8
+      for (i = 0; maps[i] != first_map; ++i)
1feee8
+	;
1feee8
+      assert (i < nmaps);
1feee8
+      memmove (&maps[1], maps, i * sizeof (maps[0]));
1feee8
+      maps[0] = first_map;
1feee8
+    }
1feee8
 }
1feee8
 
1feee8
 void
1feee8
diff --git a/elf/dso-sort-tests-1.def b/elf/dso-sort-tests-1.def
1feee8
index 5f7f18ef270bc12d..4bf9052db16fb352 100644
1feee8
--- a/elf/dso-sort-tests-1.def
1feee8
+++ b/elf/dso-sort-tests-1.def
1feee8
@@ -64,3 +64,10 @@ output: b>a>{}
1feee8
 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
1feee8
 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[
1feee8
 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[
1feee8
+
1feee8
+# Test that even in the presence of dependency loops involving dlopen'ed
1feee8
+# object, that object is initialized last (and not unloaded prematurely).
1feee8
+# Final destructor order is indeterminate due to the cycle.
1feee8
+tst-bz28937: {+a;+b;-b;+c;%c};a->a1;a->a2;a2->a;b->b1;c->a1;c=>a1
1feee8
+output(glibc.rtld.dynamic_sort=1): {+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}
1feee8
+output(glibc.rtld.dynamic_sort=2): {+a[a2>a1>a>];+b[b1>b>];-b[<b<b1];+c[c>];%c(a1());}