On Mon, Jun 2, 2025 at 8:47 AM Michal Sekletar <msekl...@redhat.com> wrote: > > 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) >
Thanks Michal, I've pushed this patch. Aaron