[PATCH v3] elf: Don't crash in dlsym when tail-called from a constructor [BZ #34156]

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon May 18 18:15:21 GMT 2026



On 18/05/26 04:39, Daan De Meyer wrote:
> If a shared library's constructor calls dlsym and discards the result,
> the compiler is free to lower the call to a tail jump.  The dynamic
> linker then resolves the apparent caller to ld.so's own link map, which
> has no l_scope, and crashes in _dl_lookup_symbol_x dereferencing the
> NULL scope pointer.
> 
> Tail-call optimization is a legal C transformation and there is no way
> for the dynamic linker to recover the real caller from the elided frame.
> Detect the situation by its observable effect -- a link map with no
> l_scope -- and fall back to the main program's link map, the same
> treatment used when the caller's address is otherwise unrecognized.
> 
> The check is written against l->l_scope rather than against _dl_rtld_map
> directly because dl-sym-post.h is also compiled into libc.so, where
> _dl_rtld_map is not visible (it lives only in ld.so).
> 
> Add dlfcn/tst-dlsym-ctor exercising the pattern.  Without the fix the
> test SIGSEGVs during dlopen; with the fix dlopen returns cleanly.
> 
> Signed-off-by: Daan De Meyer <daan@amutable.com>

LGTM, thanks.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  dlfcn/Makefile            |  4 ++++
>  dlfcn/tst-dlsym-ctor.c    | 36 ++++++++++++++++++++++++++++++++++++
>  dlfcn/tst-dlsym-ctormod.c | 28 ++++++++++++++++++++++++++++
>  elf/dl-sym-post.h         | 14 +++++++++-----
>  4 files changed, 77 insertions(+), 5 deletions(-)
>  create mode 100644 dlfcn/tst-dlsym-ctor.c
>  create mode 100644 dlfcn/tst-dlsym-ctormod.c
> 
> diff --git a/dlfcn/Makefile b/dlfcn/Makefile
> index 00341dd476..be4bd9cbe3 100644
> --- a/dlfcn/Makefile
> +++ b/dlfcn/Makefile
> @@ -67,6 +67,7 @@ tests = \
>    glrefmain \
>    tst-dladdr \
>    tst-dlinfo \
> +  tst-dlsym-ctor \
>    tst-rec-dlopen \
>    tstatexit \
>    tstcxaatexit \
> @@ -90,6 +91,7 @@ modules-names = \
>    modcxaatexit \
>    moddummy1 \
>    moddummy2 \
> +  tst-dlsym-ctormod \
>    # modules-names
>  
>  failtestmod.so-no-z-defs = yes
> @@ -197,3 +199,5 @@ $(objpfx)bug-dl-leaf.out: $(objpfx)bug-dl-leaf-lib-cb.so
>  $(objpfx)bug-dl-leaf-lib-cb.so: $(objpfx)bug-dl-leaf-lib.so
>  
>  $(objpfx)tst-rec-dlopen.out: $(objpfx)moddummy1.so $(objpfx)moddummy2.so
> +
> +$(objpfx)tst-dlsym-ctor.out: $(objpfx)tst-dlsym-ctormod.so
> diff --git a/dlfcn/tst-dlsym-ctor.c b/dlfcn/tst-dlsym-ctor.c
> new file mode 100644
> index 0000000000..948980ce2e
> --- /dev/null
> +++ b/dlfcn/tst-dlsym-ctor.c
> @@ -0,0 +1,36 @@
> +/* Test that a tail-called dlsym from a constructor works.
> +   Copyright The GNU Toolchain Authors.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <dlfcn.h>
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +
> +static int
> +do_test (void)
> +{
> +  /* Loading the module runs its constructor, which performs a dlsym
> +     whose result is discarded.  Under optimization the compiler lowers
> +     that to a tail call, so dlsym sees a caller address inside the
> +     dynamic linker itself.  Before the fix that resolved to the ld.so
> +     link map, which has no l_scope, and the lookup crashed.  */
> +  void *h = xdlopen ("tst-dlsym-ctormod.so", RTLD_NOW);
> +  xdlclose (h);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/dlfcn/tst-dlsym-ctormod.c b/dlfcn/tst-dlsym-ctormod.c
> new file mode 100644
> index 0000000000..8aeb149e8a
> --- /dev/null
> +++ b/dlfcn/tst-dlsym-ctormod.c
> @@ -0,0 +1,28 @@
> +/* Module for tst-dlsym-ctor.
> +   Copyright The GNU Toolchain Authors.
> +   This file is part of the GNU C Library.
> +
> +   The GNU C Library is free software; you can redistribute it and/or
> +   modify it under the terms of the GNU Lesser General Public
> +   License as published by the Free Software Foundation; either
> +   version 2.1 of the License, or (at your option) any later version.
> +
> +   The GNU C Library is distributed in the hope that it will be useful,
> +   but WITHOUT ANY WARRANTY; without even the implied warranty of
> +   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> +   Lesser General Public License for more details.
> +
> +   You should have received a copy of the GNU Lesser General Public
> +   License along with the GNU C Library; if not, see
> +   <https://www.gnu.org/licenses/>.  */
> +
> +#include <dlfcn.h>
> +
> +/* The result is intentionally discarded so the compiler can lower the
> +   dlsym call to a tail call.  That is the trigger for the bug -- see
> +   tst-dlsym-ctor.c.  */
> +__attribute__ ((constructor)) static void
> +ctor (void)
> +{
> +  (void) dlsym (RTLD_DEFAULT, "tst_dlsym_ctor_no_such_symbol");
> +}
> diff --git a/elf/dl-sym-post.h b/elf/dl-sym-post.h
> index 8f45298cc9..31c607defa 100644
> --- a/elf/dl-sym-post.h
> +++ b/elf/dl-sym-post.h
> @@ -22,12 +22,16 @@ static struct link_map *
>  _dl_sym_find_caller_link_map (ElfW(Addr) caller)
>  {
>    struct link_map *l = _dl_find_dso_for_object (caller);
> -  if (l != NULL)
> +  /* A constructor that tail-calls dlsym makes the caller address point
> +     into the dynamic linker itself.  The ld.so link map has no l_scope
> +     set, so using it for a symbol lookup would dereference NULL.  Treat
> +     that like an unknown caller.  */
> +  if (l != NULL && l->l_scope != NULL)
>      return l;
> -  else
> -    /* If the address is not recognized the call comes from the main
> -       program (we hope).  */
> -    return GL(dl_ns)[LM_ID_BASE]._ns_loaded;
> +  /* The address does not belong to any loaded object (e.g. it is in
> +     JIT-generated code or in the main program).  Fall back to the main
> +     program's link map.  */
> +  return GL(dl_ns)[LM_ID_BASE]._ns_loaded;
>  }
>  
>  /* Translates RESULT, *REF, VALUE into a symbol address from the point



More information about the Libc-alpha mailing list