[glibc] elf: Don't crash in dlsym when tail-called from a constructor [BZ #34156]
Adhemerval Zanella via Glibc-cvs <[email protected]> Wed, 20 May 2026 20:22:58 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=994a4378785e3c2187d1b44e5e98d97eb2369882 commit 994a4378785e3c2187d1b44e5e98d97eb2369882 Author: Daan De Meyer <[email protected]> Date: Mon May 18 07:39:33 2026 +0000 elf: Don't crash in dlsym when tail-called from a constructor [BZ #34156] 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 <[email protected]> Reviewed-by: Adhemerval Zanella <[email protected]> Diff: --- 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(-) 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