[PATCH 1/2] elf: Set up TLS slotinfo for dlopen'd modules before relocation (BZ 34170)
H.J. Lu
hjl.tools@gmail.com
Tue May 26 21:40:14 GMT 2026
On Tue, May 26, 2026 at 10:03 PM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
> An IFUNC resolver in a DSO that is being loaded by dlopen is allowed
> to read its own TLS storage during the resolver call. After
> af34b1376a3 ("elf: Initialize static TLS before relocation processing
> (BZ 34164)") that works for the initial-exec model on every supported
> architecture.
>
> However tt does not work dynamic-TLS path (on both -mtls-dialect mode,
> if the ABI supports both). Both lookup paths index the calling thread's
> DTV by the new module's l_tls_modid and, on miss, walk
> GL(dl_tls_dtv_slotinfo_list) to discover the module and lazily allocate
> its TLS block. The just-loaded DSO is however not yet in that list when
> its resolver fires, so the lookup faults inside dlopen. This is the
> direct dlopen analog of BZ 34164.
>
> The solution is to reorder dl_open_worker_begin so the slotinfo install
> happens before the relocation pass. The new order is:
>
> 1. resize_scopes, resize_tls_slotinfo, add_to_global_resize
> (unchanged, still recoverable).
> 2. update_tls_slotinfo: register the new modules in slotinfo, bump
> dl_tls_generation, initialise their static TLS images.
> 3. Relocate the new objects. IFUNC resolvers can now read their
> own DSO's __thread storage via any TLS model.
> 4. Demarcation point.
> 5. update_scopes, _dl_find_object_update.
>
> Checked on aarch64-linux-gnu and x86_64-linux-gnu.
> ---
> elf/Makefile | 23 ++++++
> elf/dl-open.c | 51 ++++++------
> elf/tst-ifunc-tls-init-gd-global-lib.c | 3 +
> elf/tst-ifunc-tls-init-gd-ld-lib-skeleton.c | 69 ++++++++++++++++
> elf/tst-ifunc-tls-init-gd-ld.c | 89 +++++++++++++++++++++
> elf/tst-ifunc-tls-init-gd-lib.c | 2 +
> elf/tst-ifunc-tls-init-ld-lib.c | 2 +
> elf/tst-ifunc-tls-init-tlsdesc-lib.c | 3 +
> elf/tst-ifunc-tls-init-tlsdesc.c | 69 ++++++++++++++++
> 9 files changed, 285 insertions(+), 26 deletions(-)
> create mode 100644 elf/tst-ifunc-tls-init-gd-global-lib.c
> create mode 100644 elf/tst-ifunc-tls-init-gd-ld-lib-skeleton.c
> create mode 100644 elf/tst-ifunc-tls-init-gd-ld.c
> create mode 100644 elf/tst-ifunc-tls-init-gd-lib.c
> create mode 100644 elf/tst-ifunc-tls-init-ld-lib.c
> create mode 100644 elf/tst-ifunc-tls-init-tlsdesc-lib.c
> create mode 100644 elf/tst-ifunc-tls-init-tlsdesc.c
>
> diff --git a/elf/Makefile b/elf/Makefile
> index d7bab52cd97..847edf4fbb6 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -1296,6 +1296,7 @@ tests += \
> tst-ifunc-plt-dlopen-bindnow \
> tst-ifunc-resolver-protector \
> tst-ifunc-tls-init \
> + tst-ifunc-tls-init-gd-ld \
> tst-ifunc-tls-write \
> # tests
> # Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8.
> @@ -1364,8 +1365,17 @@ modules-names += \
> tst-ifunc-resolver-protector-mod \
> tst-ifunc-tls-init-lib1 \
> tst-ifunc-tls-init-lib2 \
> + tst-ifunc-tls-init-gd-lib \
> + tst-ifunc-tls-init-gd-global-lib \
> + tst-ifunc-tls-init-ld-lib \
> tst-ifunc-tls-write-lib \
> # modules-names
> +ifneq (no,$(have-test-mtls-descriptor))
> +tests += tst-ifunc-tls-init-tlsdesc
> +modules-names += tst-ifunc-tls-init-tlsdesc-lib
> +CFLAGS-tst-ifunc-tls-init-tlsdesc-lib.c += \
> + -mtls-dialect=$(have-test-mtls-descriptor)
> +endif
> ifeq (no,$(with-lld))
> modules-names += ifuncmod5
> endif
> @@ -2519,6 +2529,19 @@ $(objpfx)tst-ifunc-tls-init.out: \
> $(objpfx)tst-ifunc-tls-init-lib2.so
> $(objpfx)tst-ifunc-tls-write: $(objpfx)tst-ifunc-tls-write-lib.so
>
> +$(objpfx)tst-ifunc-tls-init-gd-ld: $(shared-thread-library)
> +$(objpfx)tst-ifunc-tls-init-gd-ld.out: \
> + $(objpfx)tst-ifunc-tls-init-gd-lib.so \
> + $(objpfx)tst-ifunc-tls-init-gd-global-lib.so \
> + $(objpfx)tst-ifunc-tls-init-ld-lib.so
> +
> +ifneq (no,$(have-test-mtls-descriptor))
> +$(objpfx)tst-ifunc-tls-init-tlsdesc: $(shared-thread-library)
> +$(objpfx)tst-ifunc-tls-init-tlsdesc.out: \
> + $(objpfx)tst-ifunc-tls-init-tlsdesc-lib.so
> +tst-ifunc-tls-init-tlsdesc-TUNABLES = glibc.rtld.optional_static_tls=0
> +endif # $(have-test-mtls-descriptor)
> +
> $(objpfx)tst-unique1.out: $(objpfx)tst-unique1mod1.so \
> $(objpfx)tst-unique1mod2.so
>
> diff --git a/elf/dl-open.c b/elf/dl-open.c
> index ee25d4d42b4..460c181f3db 100644
> --- a/elf/dl-open.c
> +++ b/elf/dl-open.c
> @@ -658,6 +658,31 @@ dl_open_worker_begin (void *a)
>
> bool relocation_in_progress = false;
>
> + /* This only performs the memory allocations. The actual update of
> + the scopes happens below, after failure is impossible. */
> + resize_scopes (new);
> +
> + /* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
> + structure. */
> + bool any_tls = resize_tls_slotinfo (new);
> +
> + /* Perform the necessary allocations for adding new global objects
> + to the global scope below. */
> + if (mode & RTLD_GLOBAL)
> + add_to_global_resize (new);
> +
> + /* Install the new modules in the DTV slotinfo and initialise their
> + static TLS *before* relocation, so an IFUNC resolver firing during
> + the relocation loop below can reach its DSO's __thread storage via
> + __tls_get_addr / TLSDESC. Without this, the resolver's TLS access
> + for a just-loaded module would index into an unallocated DTV slot
> + and crash. If relocation later fails, the subsequent _dl_close_worker
> + cleans up these slotinfo entries via remove_slotinfo. */
> + if (any_tls)
> + /* FIXME: This calls _dl_update_slotinfo, which aborts the process
> + on memory allocation failure. See bug 16134. */
> + update_tls_slotinfo (new);
> +
> /* Perform relocation. This can trigger lazy binding in IFUNC
> resolvers. For NODELETE mappings, these dependencies are not
> recorded because the flag has not been applied to the newly
> @@ -682,19 +707,6 @@ dl_open_worker_begin (void *a)
> _dl_open_relocate_one_object (args, r, new->l_initfini[i], reloc_mode,
> &relocation_in_progress);
>
> - /* This only performs the memory allocations. The actual update of
> - the scopes happens below, after failure is impossible. */
> - resize_scopes (new);
> -
> - /* Increase the size of the GL (dl_tls_dtv_slotinfo_list) data
> - structure. */
> - bool any_tls = resize_tls_slotinfo (new);
> -
> - /* Perform the necessary allocations for adding new global objects
> - to the global scope below. */
> - if (mode & RTLD_GLOBAL)
> - add_to_global_resize (new);
> -
> /* Demarcation point: After this, no recoverable errors are allowed.
> All memory allocations for new objects must have happened
> before. */
> @@ -716,19 +728,6 @@ dl_open_worker_begin (void *a)
> _dl_signal_error (ENOMEM, new->l_libname->name, NULL,
> N_ ("cannot allocate address lookup data"));
>
> - /* FIXME: It is unclear whether the order here is correct.
> - Shouldn't new objects be made available for binding (and thus
> - execution) only after there TLS data has been set up fully?
> - Fixing bug 16134 will likely make this distinction less
> - important. */
> -
> - /* Second stage after resize_tls_slotinfo: Update the slotinfo data
> - structures. */
> - if (any_tls)
> - /* FIXME: This calls _dl_update_slotinfo, which aborts the process
> - on memory allocation failure. See bug 16134. */
> - update_tls_slotinfo (new);
> -
> /* Notify the debugger all new objects have been relocated. */
> if (relocation_in_progress)
> LIBC_PROBE (reloc_complete, 3, args->nsid, r, new);
> diff --git a/elf/tst-ifunc-tls-init-gd-global-lib.c b/elf/tst-ifunc-tls-init-gd-global-lib.c
> new file mode 100644
> index 00000000000..9e91ab4c9bc
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-gd-global-lib.c
> @@ -0,0 +1,3 @@
> +#define TLS_MODEL "global-dynamic"
> +#define SENTINEL_STORAGE /* empty */
> +#include "tst-ifunc-tls-init-gd-ld-lib-skeleton.c"
> diff --git a/elf/tst-ifunc-tls-init-gd-ld-lib-skeleton.c b/elf/tst-ifunc-tls-init-gd-ld-lib-skeleton.c
> new file mode 100644
> index 00000000000..b6fd6cf06e1
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-gd-ld-lib-skeleton.c
> @@ -0,0 +1,69 @@
> +/* Shared-library skeleton for tst-ifunc-tls-init-gd-ld.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + 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/>. */
> +
> +/* Unlike tst-ifunc-tls-init-lib-skeleton.c, which uses initial-exec and
> + resolves via a single TP-relative load, this skeleton's TLS_MODEL is
> + either "global-dynamic" or "local-dynamic" so the resolver's read of
> + 'sentinel' must traverse __tls_get_addr (or the architecture's
> + TLSDESC equivalent). That dynamic-TLS access can lazily allocate a
> + per-module TLS block, which is the path being exercised. */
> +
> +#ifndef TLS_MODEL
> +# error "tst-ifunc-tls-init-gd-ld-lib-skeleton.c needs TLS_MODEL defined"
> +#endif
> +
> +/* Without static the relocation against the SENTINEL goes through the regular
> + global-symbol lookup path; combined with TLS_MODEL="global-dynamic" this
> + exercises the "global GD" variant rather than the file-local one. */
> +#ifndef SENTINEL_STORAGE
> +# define SENTINEL_STORAGE static
> +#endif
> +
> +#define SENTINEL 0x5A5A1234
> +
> +SENTINEL_STORAGE volatile __thread int sentinel
> + __attribute__ ((tls_model (TLS_MODEL))) = SENTINEL;
> +static volatile int last_seen_sentinel;
> +
> +static int
> +impl_ok (void)
> +{
> + return SENTINEL;
> +}
> +
> +static int
> +impl_bad (void)
> +{
> + return 0;
> +}
> +
> +int
> +get_last_seen_sentinel (void)
> +{
> + return last_seen_sentinel;
> +}
> +
> +static int (*resolver (void)) (void)
> +{
> + int s = sentinel;
> + last_seen_sentinel = s;
> + return s == SENTINEL ? impl_ok : impl_bad;
> +}
> +int ifunc_tls (void) __attribute__ ((ifunc ("resolver")));
> +
> +int (*fptr) (void) = ifunc_tls;
> diff --git a/elf/tst-ifunc-tls-init-gd-ld.c b/elf/tst-ifunc-tls-init-gd-ld.c
> new file mode 100644
> index 00000000000..20ebe4d9931
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-gd-ld.c
> @@ -0,0 +1,89 @@
> +/* Check if dynamic-TLS variables (global-dynamic, local-dynamic) are
> + correctly initialised in IFUNC resolvers reached via dlopen.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + 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/>. */
> +
> +/* This test dlopens three modules covering the dynamic-TLS access variants:
> +
> + gd-lib file-local 'sentinel' + tls_model("global-dynamic")
> + ld-lib file-local 'sentinel' + tls_model("local-dynamic")
> + gd-global-lib external 'sentinel' + tls_model("global-dynamic")
> +
> + In each case the IFUNC resolver's read of 'sentinel' must traverse the
> + dynamic-TLS resolution path at the moment the resolver fires during
> + dlopen-time relocation.
> +
> + Each variant additionally calls the resolved IFUNC from a thread spawned
> + after dlopen, to verify per-thread DTV propagation for the newly-loaded
> + module. */
> +
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +#include <support/xthread.h>
> +
> +#define SENTINEL 0x5A5A1234
> +
> +struct ifunc_handles
> +{
> + int (*get_last_seen_sentinel) (void);
> + int (**fptr) (void);
> + int (*ifunc_tls) (void);
> +};
> +
> +static void *
> +ifunc_caller (void *arg)
> +{
> + struct ifunc_handles *h = arg;
> + TEST_COMPARE ((*h->fptr) (), SENTINEL);
> + TEST_COMPARE (h->ifunc_tls (), SENTINEL);
> + return NULL;
> +}
> +
> +static void
> +test_lib (const char *soname)
> +{
> + void *handle = xdlopen (soname, RTLD_LAZY | RTLD_LOCAL);
> +
> + struct ifunc_handles h;
> + h.get_last_seen_sentinel = xdlsym (handle, "get_last_seen_sentinel");
> + h.fptr = xdlsym (handle, "fptr");
> + h.ifunc_tls = xdlsym (handle, "ifunc_tls");
> +
> + TEST_COMPARE (h.get_last_seen_sentinel (), SENTINEL);
> +
> + TEST_VERIFY (*h.fptr != NULL);
> + TEST_COMPARE ((*h.fptr) (), SENTINEL);
> + TEST_COMPARE (h.ifunc_tls (), SENTINEL);
> +
> + /* From a thread spawned *after* the dlopen, which exercises DTV propagation
> + for the new module into a fresh TCB. */
> + pthread_t consumer = xpthread_create (NULL, ifunc_caller, &h);
> + xpthread_join (consumer);
> +
> + xdlclose (handle);
> +}
> +
> +static int
> +do_test (void)
> +{
> + test_lib ("tst-ifunc-tls-init-gd-lib.so");
> + test_lib ("tst-ifunc-tls-init-ld-lib.so");
> + test_lib ("tst-ifunc-tls-init-gd-global-lib.so");
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/elf/tst-ifunc-tls-init-gd-lib.c b/elf/tst-ifunc-tls-init-gd-lib.c
> new file mode 100644
> index 00000000000..3d940f2af3c
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-gd-lib.c
> @@ -0,0 +1,2 @@
> +#define TLS_MODEL "global-dynamic"
> +#include "tst-ifunc-tls-init-gd-ld-lib-skeleton.c"
> diff --git a/elf/tst-ifunc-tls-init-ld-lib.c b/elf/tst-ifunc-tls-init-ld-lib.c
> new file mode 100644
> index 00000000000..41e9f30298e
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-ld-lib.c
> @@ -0,0 +1,2 @@
> +#define TLS_MODEL "local-dynamic"
> +#include "tst-ifunc-tls-init-gd-ld-lib-skeleton.c"
> diff --git a/elf/tst-ifunc-tls-init-tlsdesc-lib.c b/elf/tst-ifunc-tls-init-tlsdesc-lib.c
> new file mode 100644
> index 00000000000..9e91ab4c9bc
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-tlsdesc-lib.c
> @@ -0,0 +1,3 @@
> +#define TLS_MODEL "global-dynamic"
> +#define SENTINEL_STORAGE /* empty */
> +#include "tst-ifunc-tls-init-gd-ld-lib-skeleton.c"
> diff --git a/elf/tst-ifunc-tls-init-tlsdesc.c b/elf/tst-ifunc-tls-init-tlsdesc.c
> new file mode 100644
> index 00000000000..dfa99a4248c
> --- /dev/null
> +++ b/elf/tst-ifunc-tls-init-tlsdesc.c
> @@ -0,0 +1,69 @@
> +/* Check that an IFUNC resolver in a dlopen'd DSO can read .tdata-initialised
> + __thread storage when the TLS access is compiled as a TLSDESC sequence.
> + Copyright (C) 2026 Free Software Foundation, Inc.
> + 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/>. */
> +
> +/* Sibling of tst-ifunc-tls-init-gd-ld, but the TLS access in the loaded
> + DSO is compiled with the TLSDESC dialect (x86 gnu2, aarch64 desc). */
> +
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +#include <support/xthread.h>
> +
> +#define SENTINEL 0x5A5A1234
> +
> +struct ifunc_handles
> +{
> + int (*get_last_seen_sentinel) (void);
> + int (**fptr) (void);
> + int (*ifunc_tls) (void);
> +};
> +
> +static void *
> +ifunc_caller (void *arg)
> +{
> + struct ifunc_handles *h = arg;
> + TEST_COMPARE ((*h->fptr) (), SENTINEL);
> + TEST_COMPARE (h->ifunc_tls (), SENTINEL);
> + return NULL;
> +}
> +
> +static int
> +do_test (void)
> +{
> + void *handle = xdlopen ("tst-ifunc-tls-init-tlsdesc-lib.so",
> + RTLD_LAZY | RTLD_LOCAL);
> +
> + struct ifunc_handles h;
> + h.get_last_seen_sentinel = xdlsym (handle, "get_last_seen_sentinel");
> + h.fptr = xdlsym (handle, "fptr");
> + h.ifunc_tls = xdlsym (handle, "ifunc_tls");
> +
> + TEST_COMPARE (h.get_last_seen_sentinel (), SENTINEL);
> +
> + TEST_VERIFY (*h.fptr != NULL);
> + TEST_COMPARE ((*h.fptr) (), SENTINEL);
> + TEST_COMPARE (h.ifunc_tls (), SENTINEL);
> +
> + pthread_t consumer = xpthread_create (NULL, ifunc_caller, &h);
> + xpthread_join (consumer);
> +
> + xdlclose (handle);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> --
> 2.43.0
>
LGTM.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Thanks.
--
H.J.
More information about the Libc-alpha
mailing list