[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