]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Re-initialise static TLS after .tdata relocation (BZ 34164)
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 26 May 2026 23:04:02 +0000 (20:04 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Wed, 27 May 2026 13:43:30 +0000 (10:43 -0300)
The af34b1376a37fa27e1de9d869ed9493fc569bfa6 (BZ 34164) changed the
TLS setup from:

  relocation loop (applies relocations to .tdata in DSO memory)
  _dl_allocate_tls_init copies relocated .tdata -> main thread TLS

to a new order:

  _dl_allocate_tls_init copies unrelocated .tdata -> main thread TLS
  relocation loop (relocates .tdata in DSO memory, but the TLS block
  has stale copies)

This broke file-scope thread-local initialised with the address of a
function (for instance the cache structs in libmpfr).

Fix it by splitting ELF_DYNAMIC_RELOCATE inside
_dl_relocate_object_no_relro into the non-IRELATIVE and IRELATIVE
sub-passes (similar as done on static-pie startup by b75ad99d45b)
and call _dl_init_static_tls between them.  By the time the IFUNC
pass fires, .tdata is fully relocated.

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

Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
elf/Makefile
elf/dl-reloc.c
elf/tst-tls-tdata-reloc-lib.c [new file with mode: 0644]
elf/tst-tls-tdata-reloc.c [new file with mode: 0644]

index d7bab52cd9778be69a1afba57e75083a759f20c8..a58a902451cbd41bd266ed4afb1a121987d2931a 100644 (file)
@@ -1297,6 +1297,7 @@ tests += \
   tst-ifunc-resolver-protector \
   tst-ifunc-tls-init \
   tst-ifunc-tls-write \
+  tst-tls-tdata-reloc \
   # tests
 # Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8.
 tests-internal += \
@@ -1365,6 +1366,7 @@ modules-names += \
   tst-ifunc-tls-init-lib1 \
   tst-ifunc-tls-init-lib2 \
   tst-ifunc-tls-write-lib \
+  tst-tls-tdata-reloc-lib \
   # modules-names
 ifeq (no,$(with-lld))
 modules-names += ifuncmod5
@@ -2518,6 +2520,7 @@ $(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-tls-tdata-reloc: $(objpfx)tst-tls-tdata-reloc-lib.so
 
 $(objpfx)tst-unique1.out: $(objpfx)tst-unique1mod1.so \
                          $(objpfx)tst-unique1mod2.so
index 91497b3839744efce0909c7046fe749b0ff57f21..15a6a4cffef471df2b704de7529480477e90945b 100644 (file)
@@ -269,9 +269,29 @@ _dl_relocate_object_no_relro (struct link_map *l, struct r_scope_elem *scope[],
     }
 
   {
-    /* Do the actual relocation of the object's GOT and other data.  */
+    /* Do the actual relocation of the object's GOT and other data.
 
-    ELF_DYNAMIC_RELOCATE (l, scope, lazy, consider_profiling, skip_ifunc);
+       Process the non-IRELATIVE pass first so .tdata is fully relocated
+       (including R_*_RELATIVE / R_*_64 fixups for TLS initialisers, e.g. a
+       file-scope thread-local initialised with the address of a function),
+       then refresh the static TLS slot before the IRELATIVE pass runs the
+       IFUNC resolvers.  Without this, a resolver would see the unrelocated
+       initialiser bytes that were placed into the slot by the early
+       _dl_allocate_tls_init.  */
+    ELF_DYNAMIC_RELOCATE_NOIFUNC (l, scope, lazy, consider_profiling);
+
+#ifdef SHARED
+    /* Re-initialise the static TLS slot with the .tdata so the IRELATIVE
+       pass observes a fully-relocated initialiser image.  Skipped for objects
+       without static TLS or before the main thread TCB has been set up.  */
+    if (l->l_tls_blocksize != 0
+       && __rtld_tls_init_tp_called
+       && l->l_tls_offset != NO_TLS_OFFSET
+       && l->l_tls_offset != FORCED_DYNAMIC_TLS_OFFSET)
+      _dl_init_static_tls (l);
+#endif
+
+    ELF_DYNAMIC_RELOCATE_IFUNC (l, scope, lazy, skip_ifunc);
 
     if ((consider_profiling || consider_symbind)
        && l->l_info[DT_PLTRELSZ] != NULL)
diff --git a/elf/tst-tls-tdata-reloc-lib.c b/elf/tst-tls-tdata-reloc-lib.c
new file mode 100644 (file)
index 0000000..73aa58b
--- /dev/null
@@ -0,0 +1,42 @@
+/* Shared library for tst-tls-tdata-reloc.
+   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/>.  */
+
+#define EXPECTED 0x12345678
+
+static int
+callee (void)
+{
+  return EXPECTED;
+}
+
+typedef struct
+{
+  int (*func) (void);
+} cache_t;
+
+/* General-dynamic TLS: the initialiser of '.func' produces an
+   R_*_RELATIVE on .tdata.  */
+__thread cache_t cache = { .func = callee };
+
+int
+call_through_tls (void)
+{
+  if (cache.func == 0)
+    return -1;
+  return cache.func ();
+}
diff --git a/elf/tst-tls-tdata-reloc.c b/elf/tst-tls-tdata-reloc.c
new file mode 100644 (file)
index 0000000..1d38726
--- /dev/null
@@ -0,0 +1,32 @@
+/* Check that .tdata relocations are applied to the static TLS slot.
+   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 EXPECTED 0x12345678
+
+extern int call_through_tls (void);
+
+static int
+do_test (void)
+{
+  TEST_COMPARE (call_through_tls (), EXPECTED);
+  return 0;
+}
+
+#include <support/test-driver.c>