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