Whenever possible, resolve all symlinks as if the sysroot path were a
chroot environment. This prevents potential interactions with files from
the host filesystem.

Signed-off-by: Michal Sekletar <msekl...@redhat.com>
---
 configure.ac                         | 26 ++++++++++++++++
 libdwfl/dwfl_segment_report_module.c | 44 +++++++++++++++++++++++-----
 libdwfl/link_map.c                   | 37 ++++++++++++++++++++++-
 tests/run-sysroot.sh                 | 34 +++++++++++++++++++++
 4 files changed, 132 insertions(+), 9 deletions(-)

diff --git a/configure.ac b/configure.ac
index 0670e01a..b8531aa7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -279,6 +279,32 @@ case "$CFLAGS" in
     ;;
 esac
 
+AC_CACHE_CHECK(
+  [for openat2 with RESOLVE_IN_ROOT support],
+  [eu_cv_openat2_RESOLVE_IN_ROOT],
+  [AC_LINK_IFELSE(
+    [AC_LANG_PROGRAM(
+      [[#define _GNU_SOURCE
+        #include <fcntl.h>
+        #include <stdlib.h>
+        #include <unistd.h>
+        #include <sys/syscall.h>
+        #include <linux/openat2.h>
+        #include <stdio.h>
+      ]], [[
+        struct open_how how = { .flags = O_RDONLY|O_DIRECTORY, .resolve = 
RESOLVE_IN_ROOT };
+        int dfd = open (".", O_PATH);
+        return syscall (SYS_openat2, dfd, ".", &how, sizeof(how)) < 0;
+      ]]
+    )],
+    [eu_cv_openat2_RESOLVE_IN_ROOT=yes],
+    [eu_cv_openat2_RESOLVE_IN_ROOT=no]
+  )]
+)
+AS_IF([test "x$eu_cv_openat2_RESOLVE_IN_ROOT" = xyes],
+      [AC_DEFINE([HAVE_OPENAT2_RESOLVE_IN_ROOT], [1], [Define if openat2 is 
available])]
+)
+
 dnl enable debugging of branch prediction.
 AC_ARG_ENABLE([debugpred],
 AS_HELP_STRING([--enable-debugpred],[build binaries with support to debug 
branch prediction]),
diff --git a/libdwfl/dwfl_segment_report_module.c 
b/libdwfl/dwfl_segment_report_module.c
index 32f44af8..f2f866c2 100644
--- a/libdwfl/dwfl_segment_report_module.c
+++ b/libdwfl/dwfl_segment_report_module.c
@@ -37,6 +37,12 @@
 #include <inttypes.h>
 #include <fcntl.h>
 
+#ifdef HAVE_OPENAT2_RESOLVE_IN_ROOT
+#include <linux/openat2.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#endif
+
 #include <system.h>
 
 
@@ -783,17 +789,39 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const 
char *name,
       /* We were not handed specific executable hence try to look for it in
         sysroot if it is set.  */
       if (dwfl->sysroot && !executable)
-        {
-         int r;
-         char *n;
+       {
+#ifdef HAVE_OPENAT2_RESOLVE_IN_ROOT
+         int sysrootfd, err;
+
+         struct open_how how = {
+           .flags = O_RDONLY,
+           .resolve = RESOLVE_IN_ROOT,
+         };
+
+         sysrootfd = open (dwfl->sysroot, O_DIRECTORY|O_PATH);
+         if (sysrootfd < 0)
+           return -1;
+
+         fd = syscall (SYS_openat2, sysrootfd, name, &how, sizeof(how));
+         err = fd < 0 ? -errno : 0;
 
-         r = asprintf (&n, "%s%s", dwfl->sysroot, name);
-         if (r > 0)
+         close (sysrootfd);
+
+         /* Fallback to regular open() if openat2 is not available. */
+         if (fd < 0 && err == -ENOSYS)
+#endif
            {
-             fd = open (n, O_RDONLY);
-             free (n);
+             int r;
+             char *n;
+
+             r = asprintf (&n, "%s%s", dwfl->sysroot, name);
+             if (r > 0)
+               {
+                 fd = open (n, O_RDONLY);
+                 free (n);
+               }
            }
-        }
+       }
       else
          fd = open (name, O_RDONLY);
 
diff --git a/libdwfl/link_map.c b/libdwfl/link_map.c
index 8ab14862..013a415d 100644
--- a/libdwfl/link_map.c
+++ b/libdwfl/link_map.c
@@ -34,6 +34,12 @@
 
 #include <fcntl.h>
 
+#ifdef HAVE_OPENAT2_RESOLVE_IN_ROOT
+#include <linux/openat2.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+#endif
+
 /* This element is always provided and always has a constant value.
    This makes it an easy thing to scan for to discern the format.  */
 #define PROBE_TYPE     AT_PHENT
@@ -418,19 +424,48 @@ report_r_debug (uint_fast8_t elfclass, uint_fast8_t 
elfdata,
          /* This code is mostly inlined dwfl_report_elf.  */
          char *sysroot_name = NULL;
          const char *sysroot = dwfl->sysroot;
+         int fd;
 
          /* Don't use the sysroot if the path is already inside it.  */
          bool name_in_sysroot = sysroot && startswith (name, sysroot);
 
          if (sysroot && !name_in_sysroot)
            {
+             const char *n = NULL;
+
              if (asprintf (&sysroot_name, "%s%s", sysroot, name) < 0)
                return release_buffer (&memory_closure, &buffer, 
&buffer_available, -1);
 
+             n = name;
              name = sysroot_name;
+
+#ifdef HAVE_OPENAT2_RESOLVE_IN_ROOT
+             int sysrootfd, err;
+
+             struct open_how how = {
+               .flags = O_RDONLY,
+               .resolve = RESOLVE_IN_ROOT,
+             };
+
+             sysrootfd = open (sysroot, O_DIRECTORY|O_PATH);
+             if (sysrootfd < 0)
+               return -1;
+
+             fd = syscall (SYS_openat2, sysrootfd, n, &how, sizeof(how));
+             err = fd < 0 ? -errno : 0;
+
+             close (sysrootfd);
+
+             /* Fallback to regular open() if openat2 is not available. */
+             if (fd < 0 && err == -ENOSYS)
+#endif
+               {
+                 fd = open (name, O_RDONLY);
+               }
            }
+         else
+             fd = open (name, O_RDONLY);
 
-         int fd = open (name, O_RDONLY);
          if (fd >= 0)
            {
              Elf *elf;
diff --git a/tests/run-sysroot.sh b/tests/run-sysroot.sh
index 1dc079cd..fe302446 100755
--- a/tests/run-sysroot.sh
+++ b/tests/run-sysroot.sh
@@ -46,4 +46,38 @@ TID 431185:
 #8  0x0000aaaae56127f0 _start
 EOF
 
+HAVE_OPENAT2=$(grep '^#define HAVE_OPENAT2_RESOLVE_IN_ROOT' \
+                    ${abs_builddir}/../config.h | awk '{print $3}')
+
+if [[ "$HAVE_OPENAT2" = 1 ]]; then
+    # Change the layout of files in sysroot to test symlink escape scenario
+    rm -f "${tmpdir}/sysroot/bin"
+    mkdir "${tmpdir}/sysroot/bin"
+    mv "${tmpdir}/sysroot/usr/bin/bash" "${tmpdir}/sysroot/bin/bash"
+    ln -s /bin/bash "${tmpdir}/sysroot/usr/bin/bash"
+
+    # Check that stack with --sysroot generates correct backtrace even if 
target
+    # binary is actually absolute symlink pointing outside of sysroot directory
+    testrun "${abs_top_builddir}"/src/stack --core "${tmpdir}/core.bash" \
+           --sysroot "${tmpdir}/sysroot" >"${tmpdir}/stack.out"
+
+    # Remove 2 stack frames with symbol names contained in .gnu_debugdata.
+    # Whether or not these names appear in the output depends on if elfutils
+    # was built with LZMA support.
+    sed -i '4,5d' "${tmpdir}/stack.out"
+
+    # Check that we are able to get fully symbolized backtrace
+    testrun_compare cat "${tmpdir}/stack.out" <<\EOF
+PID 431185 - core
+TID 431185:
+#0  0x0000ffff8ebe5a8c kill
+#3  0x0000aaaae562b2fc execute_command
+#4  0x0000aaaae561cbb4 reader_loop
+#5  0x0000aaaae5611bf0 main
+#6  0x0000ffff8ebd09dc __libc_start_call_main
+#7  0x0000ffff8ebd0ab0 __libc_start_main@@GLIBC_2.34
+#8  0x0000aaaae56127f0 _start
+EOF
+fi
+
 exit_cleanup
-- 
2.39.5 (Apple Git-154)

Reply via email to