1d4c55
commit 3a0588ae48fb35384a6bd33f9b66403badfa1262
1d4c55
Author: Adhemerval Zanella <adhemerval.zanella@linaro.org>
1d4c55
Date:   Tue Feb 8 15:22:49 2022 -0300
1d4c55
1d4c55
    elf: Fix DFS sorting algorithm for LD_TRACE_LOADED_OBJECTS with missing libraries (BZ #28868)
1d4c55
1d4c55
    On _dl_map_object the underlying file is not opened in trace mode
1d4c55
    (in other cases where the underlying file can't be opened,
1d4c55
    _dl_map_object  quits with an error).  If there any missing libraries
1d4c55
    being processed, they will not be considered on final nlist size
1d4c55
    passed on _dl_sort_maps later in the function.  And it is then used by
1d4c55
    _dl_sort_maps_dfs on the stack allocated working maps:
1d4c55
1d4c55
    222   /* Array to hold RPO sorting results, before we copy back to  maps[].  */
1d4c55
    223   struct link_map *rpo[nmaps];
1d4c55
    224
1d4c55
    225   /* The 'head' position during each DFS iteration. Note that we start at
1d4c55
    226      one past the last element due to first-decrement-then-store (see the
1d4c55
    227      bottom of above dfs_traversal() routine).  */
1d4c55
    228   struct link_map **rpo_head = &rpo[nmaps];
1d4c55
1d4c55
    However while transversing the 'l_initfini' on dfs_traversal it will
1d4c55
    still consider the l_faked maps and thus update rpo more times than the
1d4c55
    allocated working 'rpo', overflowing the stack object.
1d4c55
1d4c55
    As suggested in bugzilla, one option would be to avoid sorting the maps
1d4c55
    for trace mode.  However I think ignoring l_faked object does make
1d4c55
    sense (there is one less constraint to call the sorting function), it
1d4c55
    allows a slight less stack usage for trace, and it is slight simpler
1d4c55
    solution.
1d4c55
1d4c55
    The tests does trigger the stack overflow, however I tried to make
1d4c55
    it more generic to check different scenarios or missing objects.
1d4c55
1d4c55
    Checked on x86_64-linux-gnu.
1d4c55
1d4c55
    Reviewed-by: Siddhesh Poyarekar <siddhesh@sourceware.org>
1d4c55
1d4c55
Conflicts:
1d4c55
	elf/Makefile
1d4c55
	  (differences in backported tests)
1d4c55
1d4c55
diff --git a/elf/Makefile b/elf/Makefile
1d4c55
index 22a8060f7d3bb1a1..634c3113227d64a6 100644
1d4c55
--- a/elf/Makefile
1d4c55
+++ b/elf/Makefile
1d4c55
@@ -584,6 +584,11 @@ modules-names = \
1d4c55
   libmarkermod5-3 \
1d4c55
   libmarkermod5-4 \
1d4c55
   libmarkermod5-5 \
1d4c55
+  libtracemod1-1 \
1d4c55
+  libtracemod2-1 \
1d4c55
+  libtracemod3-1 \
1d4c55
+  libtracemod4-1 \
1d4c55
+  libtracemod5-1 \
1d4c55
   ltglobmod1 \
1d4c55
   ltglobmod2 \
1d4c55
   neededobj1 \
1d4c55
@@ -983,6 +988,11 @@ tests-special += \
1d4c55
   $(objpfx)tst-initorder2-cmp.out \
1d4c55
   $(objpfx)tst-unused-dep-cmp.out \
1d4c55
   $(objpfx)tst-unused-dep.out \
1d4c55
+  $(objpfx)tst-trace1.out \
1d4c55
+  $(objpfx)tst-trace2.out \
1d4c55
+  $(objpfx)tst-trace3.out \
1d4c55
+  $(objpfx)tst-trace4.out \
1d4c55
+  $(objpfx)tst-trace5.out \
1d4c55
   # tests-special
1d4c55
 endif
1d4c55
 
1d4c55
@@ -2619,6 +2629,51 @@ $(objpfx)tst-rtld-run-static.out: $(objpfx)/ldconfig
1d4c55
 
1d4c55
 $(objpfx)tst-dlmopen-gethostbyname: $(libdl)
1d4c55
 $(objpfx)tst-dlmopen-gethostbyname.out: $(objpfx)tst-dlmopen-gethostbyname-mod.so
1d4c55
+
1d4c55
+LDFLAGS-libtracemod1-1.so += -Wl,-soname,libtracemod1.so
1d4c55
+LDFLAGS-libtracemod2-1.so += -Wl,-soname,libtracemod2.so
1d4c55
+LDFLAGS-libtracemod3-1.so += -Wl,-soname,libtracemod3.so
1d4c55
+LDFLAGS-libtracemod4-1.so += -Wl,-soname,libtracemod4.so
1d4c55
+LDFLAGS-libtracemod5-1.so += -Wl,-soname,libtracemod5.so
1d4c55
+
1d4c55
+$(objpfx)libtracemod1-1.so: $(objpfx)libtracemod2-1.so \
1d4c55
+			    $(objpfx)libtracemod3-1.so
1d4c55
+$(objpfx)libtracemod2-1.so: $(objpfx)libtracemod4-1.so \
1d4c55
+			    $(objpfx)libtracemod5-1.so
1d4c55
+
1d4c55
+define libtracemod-x
1d4c55
+$(objpfx)libtracemod$(1)/libtracemod$(1).so: $(objpfx)libtracemod$(1)-1.so
1d4c55
+	$$(make-target-directory)
1d4c55
+	cp $$< $$@
1d4c55
+endef
1d4c55
+libtracemod-suffixes = 1 2 3 4 5
1d4c55
+$(foreach i,$(libtracemod-suffixes), $(eval $(call libtracemod-x,$(i))))
1d4c55
+
1d4c55
+define tst-trace-skeleton
1d4c55
+$(objpfx)tst-trace$(1).out: $(objpfx)libtracemod1/libtracemod1.so \
1d4c55
+			    $(objpfx)libtracemod2/libtracemod2.so \
1d4c55
+			    $(objpfx)libtracemod3/libtracemod3.so \
1d4c55
+			    $(objpfx)libtracemod4/libtracemod4.so \
1d4c55
+			    $(objpfx)libtracemod5/libtracemod5.so \
1d4c55
+			    $(..)scripts/tst-ld-trace.py \
1d4c55
+			    tst-trace$(1).exp
1d4c55
+	${ $(PYTHON) $(..)scripts/tst-ld-trace.py \
1d4c55
+	    "$(test-wrapper-env) $(elf-objpfx)$(rtld-installed-name) \
1d4c55
+	    --library-path $(common-objpfx):$(strip $(2)) \
1d4c55
+	    $(objpfx)libtracemod1/libtracemod1.so" tst-trace$(1).exp \
1d4c55
+	} > $$@; $$(evaluate-test)
1d4c55
+endef
1d4c55
+
1d4c55
+$(eval $(call tst-trace-skeleton,1,))
1d4c55
+$(eval $(call tst-trace-skeleton,2,\
1d4c55
+	$(objpfx)libtracemod2))
1d4c55
+$(eval $(call tst-trace-skeleton,3,\
1d4c55
+	$(objpfx)libtracemod2:$(objpfx)libtracemod3))
1d4c55
+$(eval $(call tst-trace-skeleton,4,\
1d4c55
+	$(objpfx)libtracemod2:$(objpfx)libtracemod3:$(objpfx)libtracemod4))
1d4c55
+$(eval $(call tst-trace-skeleton,5,\
1d4c55
+	$(objpfx)libtracemod2:$(objpfx)libtracemod3:$(objpfx)libtracemod4:$(objpfx)libtracemod5))
1d4c55
+
1d4c55
 $(objpfx)tst-audit-tlsdesc: $(objpfx)tst-audit-tlsdesc-mod1.so \
1d4c55
 			    $(objpfx)tst-audit-tlsdesc-mod2.so \
1d4c55
 			    $(shared-thread-library)
1d4c55
diff --git a/elf/dl-deps.c b/elf/dl-deps.c
1d4c55
index 9365d54c8e03e5f4..9ff589c8562b2dd1 100644
1d4c55
--- a/elf/dl-deps.c
1d4c55
+++ b/elf/dl-deps.c
1d4c55
@@ -489,6 +489,8 @@ _dl_map_object_deps (struct link_map *map,
1d4c55
 
1d4c55
   for (nlist = 0, runp = known; runp; runp = runp->next)
1d4c55
     {
1d4c55
+      /* _dl_sort_maps ignores l_faked object, so it is safe to not consider
1d4c55
+	 them for nlist.  */
1d4c55
       if (__builtin_expect (trace_mode, 0) && runp->map->l_faked)
1d4c55
 	/* This can happen when we trace the loading.  */
1d4c55
 	--map->l_searchlist.r_nlist;
1d4c55
diff --git a/elf/dl-sort-maps.c b/elf/dl-sort-maps.c
1d4c55
index 398a08f28c4d9ff1..99354dc08a010dd3 100644
1d4c55
--- a/elf/dl-sort-maps.c
1d4c55
+++ b/elf/dl-sort-maps.c
1d4c55
@@ -140,7 +140,9 @@ static void
1d4c55
 dfs_traversal (struct link_map ***rpo, struct link_map *map,
1d4c55
 	       bool *do_reldeps)
1d4c55
 {
1d4c55
-  if (map->l_visited)
1d4c55
+  /* _dl_map_object_deps ignores l_faked objects when calculating the
1d4c55
+     number of maps before calling _dl_sort_maps, ignore them as well.  */
1d4c55
+  if (map->l_visited || map->l_faked)
1d4c55
     return;
1d4c55
 
1d4c55
   map->l_visited = 1;
1d4c55
diff --git a/elf/libtracemod1-1.c b/elf/libtracemod1-1.c
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..7c89c9a5a40b9668
1d4c55
--- /dev/null
1d4c55
+++ b/elf/libtracemod1-1.c
1d4c55
@@ -0,0 +1 @@
1d4c55
+/* Empty  */
1d4c55
diff --git a/elf/libtracemod2-1.c b/elf/libtracemod2-1.c
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..7c89c9a5a40b9668
1d4c55
--- /dev/null
1d4c55
+++ b/elf/libtracemod2-1.c
1d4c55
@@ -0,0 +1 @@
1d4c55
+/* Empty  */
1d4c55
diff --git a/elf/libtracemod3-1.c b/elf/libtracemod3-1.c
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..7c89c9a5a40b9668
1d4c55
--- /dev/null
1d4c55
+++ b/elf/libtracemod3-1.c
1d4c55
@@ -0,0 +1 @@
1d4c55
+/* Empty  */
1d4c55
diff --git a/elf/libtracemod4-1.c b/elf/libtracemod4-1.c
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..7c89c9a5a40b9668
1d4c55
--- /dev/null
1d4c55
+++ b/elf/libtracemod4-1.c
1d4c55
@@ -0,0 +1 @@
1d4c55
+/* Empty  */
1d4c55
diff --git a/elf/libtracemod5-1.c b/elf/libtracemod5-1.c
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..7c89c9a5a40b9668
1d4c55
--- /dev/null
1d4c55
+++ b/elf/libtracemod5-1.c
1d4c55
@@ -0,0 +1 @@
1d4c55
+/* Empty  */
1d4c55
diff --git a/elf/tst-trace1.exp b/elf/tst-trace1.exp
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..4a6f5211a68fe2c8
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-trace1.exp
1d4c55
@@ -0,0 +1,4 @@
1d4c55
+ld 1
1d4c55
+libc 1
1d4c55
+libtracemod2.so 0
1d4c55
+libtracemod3.so 0
1d4c55
diff --git a/elf/tst-trace2.exp b/elf/tst-trace2.exp
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..e13506e2eb9aeca2
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-trace2.exp
1d4c55
@@ -0,0 +1,6 @@
1d4c55
+ld 1
1d4c55
+libc 1
1d4c55
+libtracemod2.so 1
1d4c55
+libtracemod3.so 0
1d4c55
+libtracemod4.so 0
1d4c55
+libtracemod5.so 0
1d4c55
diff --git a/elf/tst-trace3.exp b/elf/tst-trace3.exp
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..e574549d12a53d72
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-trace3.exp
1d4c55
@@ -0,0 +1,6 @@
1d4c55
+ld 1
1d4c55
+libc 1
1d4c55
+libtracemod2.so 1
1d4c55
+libtracemod3.so 1
1d4c55
+libtracemod4.so 0
1d4c55
+libtracemod5.so 0
1d4c55
diff --git a/elf/tst-trace4.exp b/elf/tst-trace4.exp
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..31ca97b35bde0009
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-trace4.exp
1d4c55
@@ -0,0 +1,6 @@
1d4c55
+ld 1
1d4c55
+libc 1
1d4c55
+libtracemod2.so 1
1d4c55
+libtracemod3.so 1
1d4c55
+libtracemod4.so 1
1d4c55
+libtracemod5.so 0
1d4c55
diff --git a/elf/tst-trace5.exp b/elf/tst-trace5.exp
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..5d7d95372656396f
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-trace5.exp
1d4c55
@@ -0,0 +1,6 @@
1d4c55
+ld 1
1d4c55
+libc 1
1d4c55
+libtracemod2.so 1
1d4c55
+libtracemod3.so 1
1d4c55
+libtracemod4.so 1
1d4c55
+libtracemod5.so 1
1d4c55
diff --git a/scripts/tst-ld-trace.py b/scripts/tst-ld-trace.py
1d4c55
new file mode 100755
1d4c55
index 0000000000000000..f5a402800377f44b
1d4c55
--- /dev/null
1d4c55
+++ b/scripts/tst-ld-trace.py
1d4c55
@@ -0,0 +1,108 @@
1d4c55
+#!/usr/bin/python3
1d4c55
+# Dump the output of LD_TRACE_LOADED_OBJECTS in architecture neutral format.
1d4c55
+# Copyright (C) 2022 Free Software Foundation, Inc.
1d4c55
+# Copyright The GNU Toolchain Authors.
1d4c55
+# This file is part of the GNU C Library.
1d4c55
+#
1d4c55
+# The GNU C Library is free software; you can redistribute it and/or
1d4c55
+# modify it under the terms of the GNU Lesser General Public
1d4c55
+# License as published by the Free Software Foundation; either
1d4c55
+# version 2.1 of the License, or (at your option) any later version.
1d4c55
+#
1d4c55
+# The GNU C Library is distributed in the hope that it will be useful,
1d4c55
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1d4c55
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1d4c55
+# Lesser General Public License for more details.
1d4c55
+#
1d4c55
+# You should have received a copy of the GNU Lesser General Public
1d4c55
+# License along with the GNU C Library; if not, see
1d4c55
+# <https://www.gnu.org/licenses/>.
1d4c55
+
1d4c55
+import argparse
1d4c55
+import os
1d4c55
+import subprocess
1d4c55
+import sys
1d4c55
+
1d4c55
+try:
1d4c55
+    subprocess.run
1d4c55
+except:
1d4c55
+    class _CompletedProcess:
1d4c55
+        def __init__(self, args, returncode, stdout=None, stderr=None):
1d4c55
+            self.args = args
1d4c55
+            self.returncode = returncode
1d4c55
+            self.stdout = stdout
1d4c55
+            self.stderr = stderr
1d4c55
+
1d4c55
+    def _run(*popenargs, input=None, timeout=None, check=False, **kwargs):
1d4c55
+        assert(timeout is None)
1d4c55
+        with subprocess.Popen(*popenargs, **kwargs) as process:
1d4c55
+            try:
1d4c55
+                stdout, stderr = process.communicate(input)
1d4c55
+            except:
1d4c55
+                process.kill()
1d4c55
+                process.wait()
1d4c55
+                raise
1d4c55
+            returncode = process.poll()
1d4c55
+            if check and returncode:
1d4c55
+                raise subprocess.CalledProcessError(returncode, popenargs)
1d4c55
+        return _CompletedProcess(popenargs, returncode, stdout, stderr)
1d4c55
+
1d4c55
+    subprocess.run = _run
1d4c55
+
1d4c55
+def is_vdso(lib):
1d4c55
+    return lib.startswith('linux-gate') or lib.startswith('linux-vdso')
1d4c55
+
1d4c55
+
1d4c55
+def parse_trace(cmd, fref):
1d4c55
+    new_env = os.environ.copy()
1d4c55
+    new_env['LD_TRACE_LOADED_OBJECTS'] = '1'
1d4c55
+    trace_out = subprocess.run(cmd, stdout=subprocess.PIPE, check=True,
1d4c55
+                               universal_newlines=True, env=new_env).stdout
1d4c55
+    trace = []
1d4c55
+    for line in trace_out.splitlines():
1d4c55
+        line = line.strip()
1d4c55
+        if is_vdso(line):
1d4c55
+            continue
1d4c55
+        fields = line.split('=>' if '=>' in line else ' ')
1d4c55
+        lib = os.path.basename(fields[0].strip())
1d4c55
+        if lib.startswith('ld'):
1d4c55
+            lib = 'ld'
1d4c55
+        elif lib.startswith('libc'):
1d4c55
+            lib = 'libc'
1d4c55
+        found = 1 if fields[1].strip() != 'not found' else 0
1d4c55
+        trace += ['{} {}'.format(lib, found)]
1d4c55
+    trace = sorted(trace)
1d4c55
+
1d4c55
+    reference = sorted(line.replace('\n','') for line in fref.readlines())
1d4c55
+
1d4c55
+    ret = 0 if trace == reference else 1
1d4c55
+    if ret != 0:
1d4c55
+        for i in reference:
1d4c55
+            if i not in trace:
1d4c55
+                print("Only in {}: {}".format(fref.name, i))
1d4c55
+        for i in trace:
1d4c55
+            if i not in reference:
1d4c55
+                print("Only in trace: {}".format(i))
1d4c55
+
1d4c55
+    sys.exit(ret)
1d4c55
+
1d4c55
+
1d4c55
+def get_parser():
1d4c55
+    parser = argparse.ArgumentParser(description=__doc__)
1d4c55
+    parser.add_argument('command',
1d4c55
+                        help='comand to run')
1d4c55
+    parser.add_argument('reference',
1d4c55
+                        help='reference file to compare')
1d4c55
+    return parser
1d4c55
+
1d4c55
+
1d4c55
+def main(argv):
1d4c55
+    parser = get_parser()
1d4c55
+    opts = parser.parse_args(argv)
1d4c55
+    with open(opts.reference, 'r') as fref:
1d4c55
+        # Remove the initial 'env' command.
1d4c55
+        parse_trace(opts.command.split()[1:], fref)
1d4c55
+
1d4c55
+
1d4c55
+if __name__ == '__main__':
1d4c55
+    main(sys.argv[1:])