mirror of
https://sourceware.org/git/glibc.git
synced 2026-10-28 18:35:30 +08:00
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:
1 parent
e563a57105
commit
af34b1376a
10 files changed
+327
-24
No files matched your search
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1 @@
|
||||
#include "tst-ifunc-tls-init-lib-skeleton.c"
|
||||
@@ -0,0 +1 @@
|
||||
#include "tst-ifunc-tls-init-lib-skeleton.c"
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
Reference in new issue
Block a user