[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