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

Reply via email to