elf: Initialize static TLS before relocation processing (BZ 34164)

An IFUNC resolver firing during dynamic linker relocation reads its
DSO's __thread storage from a zero-filled slot: init_tls() allocates
the static TLS block zero-filled, but .tdata is not copied in until
the trailing _dl_allocate_tls_init at the end of dl_main, long after
the per-object phase 2 resolvers from commit 63b31c05a8 have run.
A resolver that *writes* TLS is even worse off -- the write is
clobbered by that same trailing copy.

dl_main (elf/rtld.c): populate the DTV slotinfo, bump
dl_tls_generation, and call _dl_allocate_tls_init right after
init_tls(), before the relocation loop.

_dl_try_allocate_static_tls (elf/dl-reloc.c): drop the
"defer-if-not-relocated" branch and always run _dl_init_static_tls
inline, so a CHECK_STATIC_TLS allocation triggered mid-relocation
initialises the slot before the same object's phase 2 fires.

The new tests cheks some scenarios:

  elf/tst-ifunc-tls-init         resolver reads its DSO's IE TLS.
  elf/tst-ifunc-tls-init-dlopen  same, via dlopen.
  elf/tst-ifunc-tls-write        resolver write to TLS must survive
                                 to main.

Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.

Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
This commit is contained in:
Adhemerval Zanella committed 2026-05-25 12:49:14 +00:00
1 parent e563a57105
commit af34b1376a
10 files changed
+327 -24

No files matched your search

+10
View File
@@ -1268,6 +1268,8 @@ tests += \
tst-ifunc-plt-bindnow \
tst-ifunc-plt-dlopen \
tst-ifunc-plt-dlopen-bindnow \
tst-ifunc-tls-init \
tst-ifunc-tls-write \
# tests
# Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8.
tests-internal += \
@@ -1332,6 +1334,9 @@ modules-names += \
ifuncmod6 \
tst-ifunc-plt-dep \
tst-ifunc-plt-lib \
tst-ifunc-tls-init-lib1 \
tst-ifunc-tls-init-lib2 \
tst-ifunc-tls-write-lib \
# modules-names
ifeq (no,$(with-lld))
modules-names += ifuncmod5
@@ -2468,6 +2473,11 @@ $(objpfx)tst-ifunc-plt-dlopen.out: \
$(objpfx)tst-ifunc-plt-dlopen-bindnow.out: \
$(objpfx)tst-ifunc-plt-lib.so $(objpfx)tst-ifunc-plt-dep.so
$(objpfx)tst-ifunc-tls-init: $(objpfx)tst-ifunc-tls-init-lib1.so
$(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-unique1.out: $(objpfx)tst-unique1mod1.so \
$(objpfx)tst-unique1mod2.so
+13 -14
View File
@@ -107,22 +107,21 @@ _dl_try_allocate_static_tls (struct link_map *map, bool optional)
# error "Either TLS_TCB_AT_TP or TLS_DTV_AT_TP must be defined"
#endif
/* If the object is not yet relocated we cannot initialize the
static TLS region. Delay it. */
if (map->l_real->l_relocated)
{
/* Initialise the static TLS region, the map may not yet be l_relocated (a
TLS reloc inside the relocation loop triggered the allocation), but
_dl_init_static_tls only writes .tdata into the static TLS slot, which is
independent of relocation state.
Doing this inline ensures any IFUNC resolver that fires later in the same
object's relocation pass sees an initialised TLS slot, and the
post-relocation TLS init loop in dl_open_worker_begin becomes a no-op for
this map. */
#ifdef SHARED
/* Update the DTV of the current thread. Note: GL(dl_load_tls_lock)
is held here so normal load of the generation counter is valid. */
if (__builtin_expect (THREAD_DTV()[0].counter != GL(dl_tls_generation),
0))
(void) _dl_update_slotinfo (map->l_tls_modid, GL(dl_tls_generation));
/* Update the DTV of the current thread. Note: GL(dl_load_tls_lock)
is held here so normal load of the generation counter is valid. */
if (__glibc_unlikely (THREAD_DTV()[0].counter != GL(dl_tls_generation)))
_dl_update_slotinfo (map->l_tls_modid, GL(dl_tls_generation));
#endif
_dl_init_static_tls (map);
}
else
map->l_need_tls_init = 1;
_dl_init_static_tls (map);
return 0;
}
+21 -10
View File
@@ -2263,6 +2263,26 @@ dl_main (const ElfW(Phdr) *phdr,
rtld_timer_accum (&relocate_time, start);
}
/* Populate the DTV slotinfo and copy each TLS module's into thestatic TLS
block *before* the relocation loop. IFUNC resolvers fired during phase 2
of the per-object two-phase scheme therefore observe initialised TLS. */
if (__rtld_tls_init_tp_called)
{
unsigned int i = main_map->l_searchlist.r_nlist;
while (i-- > 0)
{
struct link_map *l = main_map->l_initfini[i];
if (l->l_tls_blocksize != 0)
_dl_add_to_slotinfo (l, true);
}
/* _dl_add_to_slotinfo records gen = dl_tls_generation + 1, and
_dl_allocate_tls_init asserts gen <= dl_tls_generation, so bump
the generation before init. */
if (GL(dl_tls_max_dtv_idx) > 0)
++GL(dl_tls_generation);
_dl_allocate_tls_init (tcbp, true);
}
RTLD_TIMING_VAR (start);
rtld_timer_start (&start);
{
@@ -2286,10 +2306,6 @@ dl_main (const ElfW(Phdr) *phdr,
_dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
consider_profiling);
/* Add object to slot information data if necessary. */
if (l->l_tls_blocksize != 0 && __rtld_tls_init_tp_called)
_dl_add_to_slotinfo (l, true);
}
}
rtld_timer_stop (&relocate_time, start);
@@ -2310,12 +2326,7 @@ dl_main (const ElfW(Phdr) *phdr,
|| count_modids != _dl_count_modids ())
++GL(dl_tls_generation);
/* Now that we have completed relocation, the initializer data
for the TLS blocks has its final values and we can copy them
into the main thread's TLS area, which we allocated above.
Note: thread-local variables must only be accessed after completing
the next step. */
_dl_allocate_tls_init (tcbp, true);
/* TLS .tdata copy moved before the relocation loop above. */
/* And finally install it for the main thread. */
if (! __rtld_tls_init_tp_called)
+55
View File
@@ -0,0 +1,55 @@
/* Shared library for tst-ifunc-tls-init-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/>. */
/* Same as tst-ifunc-tls-init-lib.c, but exercises the dlopen path. */
#define SENTINEL 0x5A5A1234
static volatile __thread int sentinel
__attribute__ ((tls_model ("initial-exec"))) = 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")));
/* Force a non-PLT relocation against the IFUNC symbol. */
int (*fptr) (void) = ifunc_tls;
+57
View File
@@ -0,0 +1,57 @@
/* Test that static-TLS initialisation works correctly with IFUNC resolvers.
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/>. */
/* The initial-exec TLS model keeps the access path to a single TP-relative
load, so the test is sensitive to whether the static TLS block has been
populated rather than to any __tls_get_addr / DTV-update timing. */
#define SENTINEL 0x5A5A1234
/* The 'volatile' avoids constant fold optimization in impl_ok. */
static volatile __thread int sentinel
__attribute__ ((tls_model ("initial-exec"))) = 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")));
/* Force a non-PLT relocation against the IFUNC symbol. */
int (*fptr) (void) = ifunc_tls;
+1
View File
@@ -0,0 +1 @@
#include "tst-ifunc-tls-init-lib-skeleton.c"
+1
View File
@@ -0,0 +1 @@
#include "tst-ifunc-tls-init-lib-skeleton.c"
+81
View File
@@ -0,0 +1,81 @@
/* Check if static-TLS variables are correctly intialized in IFUNC resolvers.
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/>. */
/* Checks if a IFUNC resolver sees if a TLS blocker is properly initialized.
The tst-ifunc-tls-init-lib.so carries:
- a thread-local 'sentinel' initialised to SENTINEL.
- an IFUNC 'ifunc_tls' whose resolver picks impl_ok when sentinel reads
as SENTINEL and impl_bad (returning 0) otherwise.
- an IFUNC-backed function-pointer global 'fptr' whose initialiser
produces a non-PLT relocation. */
#include <support/check.h>
#include <support/xdlfcn.h>
#define SENTINEL 0x5A5A1234
extern int ifunc_tls (void);
extern int (*fptr) (void);
extern int get_last_seen_sentinel (void);
static void
test_tls_ifunc (int (*check_sentinel)(void),
int (*check_fptr)(void),
int (*check_ifunc_tls)(void))
{
/* Primary check: 'get_last_seen_sentinel' returns the value of the DSO's
thread-local 'sentinel' as observed by the resolver at the moment it ran
for the IFUNC reloc that initialised fptr. The getter is a regular
PLT-resolved function in the DSO, so the read of the diagnostic global
does NOT go through a COPY relocation that could overwrite the resolver's
write. */
TEST_COMPARE (check_sentinel (), SENTINEL);
/* Secondary check: fptr is set during IFUNC resolver call, then copied into
the exe's. Returns SENTINEL only if the resolver picked impl_ok. */
TEST_VERIFY (check_fptr != NULL);
TEST_COMPARE (check_fptr (), SENTINEL);
/* Sanity check: issue the ifunc. */
TEST_COMPARE (check_ifunc_tls (), SENTINEL);
}
static int
do_test (void)
{
test_tls_ifunc (get_last_seen_sentinel, fptr, ifunc_tls);
/* Same as before, but check the dlopen path. */
void *handle = xdlopen ("tst-ifunc-tls-init-lib2.so",
RTLD_LAZY | RTLD_LOCAL);
int (*get_last_seen_sentinel_dlopen) (void)
= xdlsym (handle, "get_last_seen_sentinel");
int (**fptr_dlopen) (void) = xdlsym (handle, "fptr");
int (*ifunc_tls_dlopen) (void) = xdlsym (handle, "ifunc_tls");
test_tls_ifunc (get_last_seen_sentinel_dlopen, *fptr_dlopen,
ifunc_tls_dlopen);
xdlclose (handle);
return 0;
}
#include <support/test-driver.c>
+54
View File
@@ -0,0 +1,54 @@
/* Shared library for tst-ifunc-tls-write.
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/>. */
/* The DSO defines an initial-exec thread-local 'counter' initialised to
SENTINEL, the IFUNC resolver (via the non-PLT IFUNC reloc on 'fptr')
overwrites counter with MARKER, and the test then reads counter back
through a getter. */
#define SENTINEL 0x5A5A1234u
#define MARKER 0x32125A5Au
/* The 'volatile' avoids constant fold optimization in impl_ok. */
static volatile __thread unsigned int counter
__attribute__ ((tls_model ("initial-exec"))) = SENTINEL;
static unsigned int
impl (void)
{
return 0;
}
static unsigned int (*resolver (void)) (void)
{
counter = MARKER;
return impl;
}
unsigned int ifunc_write (void) __attribute__ ((ifunc ("resolver")));
/* Force the resolver rather than lazy bind on first call, which would
re-write counter after the test reads it). Using a COPY'd fptr also lets
the test verify the resolver ran without making a PLT call that would
itself fire the resolver again. */
unsigned int (*fptr) (void) = ifunc_write;
unsigned int
get_counter (void)
{
return counter;
}
+34
View File
@@ -0,0 +1,34 @@
/* Check if static-TLS variables are correctly intialized in IFUNC resolvers.
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/>. */
#include <support/check.h>
#define MARKER 0x32125A5Au
extern unsigned int (*fptr) (void);
extern unsigned int get_counter (void);
static int
do_test (void)
{
TEST_VERIFY (fptr != NULL);
TEST_COMPARE (get_counter (), MARKER);
return 0;
}
#include <support/test-driver.c>