[1/2] elf: Set up TLS slotinfo for dlopen'd modules before relocation (BZ 34170)

Message ID 20260526140248.603938-2-adhemerval.zanella@linaro.org (mailing list archive)
State New
Headers
Series Fix IFUNC resolvers in dlopen'd modules and improve instrumentation |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 fail Test failed
linaro-tcwg-bot/tcwg_glibc_check--master-arm fail Test failed

Commit Message

Adhemerval Zanella Netto May 26, 2026, 2 p.m. UTC
  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
  

Comments

H.J. Lu May 26, 2026, 9:40 p.m. UTC | #1
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.
  

Patch

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>