[PATCH 2/2] elf: Initialize TCB and stack-protector before static IFUNC resolvers (BZ 20680, BZ 27582, BZ 28817)
H.J. Lu
hjl.tools@gmail.com
Fri May 22 22:21:06 GMT 2026
On Sat, May 23, 2026 at 1:11 AM Adhemerval Zanella
<adhemerval.zanella@linaro.org> wrote:
>
> In static linking the IFUNC IPLT (apply_irel for non-PIE, the IRELATIVE
> phase inside _dl_relocate_static_pie for static-pie) ran before
> __libc_setup_tls and before _dl_setup_stack_chk_guard. When a resolver
> is compiled with -fstack-protector(-all) its prologue loads the canary
> from the TCB (TCB-canary ABIs: x86_64, i386, powerpc, s390) or from
> __stack_chk_guard (global-var ABIs). On the former the resolver
> crashed reading an unmapped TCB; on the latter it loaded a zero canary
> (no crash, but the check is ineffective). The same applies to a
> resolver that reads any thread-local: it crashes on TCB-canary ABIs and
> observes a zero-filled slot on the others (BZ 20680). The pointer
> guard has the same problem (e.g. resolvers that register an atexit
> handler).
>
> Reorder csu/libc-start.c so that ARCH_SETUP_TLS, the stack-protector
> canary and the pointer guard are set up before any IFUNC resolver
> runs. For static-pie this requires splitting the existing
> _dl_relocate_static_pie into two phases so the TCB/canary setup can be
> interleaved between the non-IRELATIVE and IRELATIVE passes.
>
> The historical ARCH_SETUP_IREL / ARCH_APPLY_IREL split (introduced for
> powerpc so its IFUNC resolvers could read TCB fields like hwcap and
> at_platform) is no longer required: TLS is now set up before either
> macro runs. ARCH_APPLY_IREL is removed, ARCH_SETUP_IREL does the work
> uniformly on every arch, and the powerpc-specific libc-start.h becomes
> redundant.
>
> __libc_setup_tls reaches memcpy / mempcpy via _dl_allocate_tls_init in
> elf/dl-tls.c, so it requires update ABI specific dl-symbol-redir-ifunc.h
> with memcpy/memmove.
>
> Tests added (each fails pre-fix on TCB-canary ABIs with SIGSEGV; the
> static-protector variants additionally fail on global-var ABIs with a
> "resolver_canary != main_canary" diagnostic):
>
> elf/tst-ifunc-bz28817 static-pie + TLS in
> resolver (BZ 28817)
> elf/tst-ifunc-resolver-protector dynamic
> elf/tst-ifunc-resolver-protector-static static-pie
> elf/tst-ifunc-resolver-protector-static-non-pie non-PIE static
>
> Checked on aarch64-linux-gnu, arm-linux-gnueabihf, x86_64-linux-gnu,
> and i686-linux-gnu
>
> I also ran the ELF tests on qemu system for loongarch64-linux-gnuf64,
> powerpc-linux-gnu, powerpc-linux-gnu-power4, powerpc-linux-gnu-soft,
> powerpc64-linux-gnu, powerpc64le-linux-gnu, riscv64-linux-gnu, and
> s390x-linux-gnu.
> ---
> csu/libc-start.c | 40 +++++----
> csu/static-reloc.c | 5 ++
> elf/Makefile | 43 +++++++++-
> elf/dl-reloc-static-pie.c | 24 ++++--
> elf/dynamic-link.h | 84 +++++++++++++++----
> elf/tst-ifunc-bz28817.c | 60 +++++++++++++
> elf/tst-ifunc-resolver-protector-mod.c | 61 ++++++++++++++
> elf/tst-ifunc-resolver-protector-static-mod.c | 68 +++++++++++++++
> ...nc-resolver-protector-static-non-pie-mod.c | 2 +
> ...-ifunc-resolver-protector-static-non-pie.c | 5 ++
> elf/tst-ifunc-resolver-protector-static.c | 61 ++++++++++++++
> elf/tst-ifunc-resolver-protector.c | 42 ++++++++++
> .../aarch64/multiarch/dl-symbol-redir-ifunc.h | 4 +
> sysdeps/generic/ldsodefs.h | 7 +-
> sysdeps/generic/libc-start.h | 9 +-
> .../lp64/multiarch/dl-symbol-redir-ifunc.h | 2 +
> sysdeps/unix/sysv/linux/aarch64/libc-start.h | 1 -
> sysdeps/unix/sysv/linux/powerpc/libc-start.h | 30 -------
> sysdeps/x86_64/libc-start.h | 1 -
> .../x86_64/multiarch/dl-symbol-redir-ifunc.h | 19 +++++
> 20 files changed, 490 insertions(+), 78 deletions(-)
> create mode 100644 elf/tst-ifunc-bz28817.c
> create mode 100644 elf/tst-ifunc-resolver-protector-mod.c
> create mode 100644 elf/tst-ifunc-resolver-protector-static-mod.c
> create mode 100644 elf/tst-ifunc-resolver-protector-static-non-pie-mod.c
> create mode 100644 elf/tst-ifunc-resolver-protector-static-non-pie.c
> create mode 100644 elf/tst-ifunc-resolver-protector-static.c
> create mode 100644 elf/tst-ifunc-resolver-protector.c
> delete mode 100644 sysdeps/unix/sysv/linux/powerpc/libc-start.h
>
> diff --git a/csu/libc-start.c b/csu/libc-start.c
> index 1c58561bce0..03d770ef157 100644
> --- a/csu/libc-start.c
> +++ b/csu/libc-start.c
> @@ -268,23 +268,22 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
>
> ARCH_INIT_CPU_FEATURES ();
>
> - /* Do static pie self relocation after tunables and cpu features
> - are setup for ifunc resolvers. Before this point relocations
> - must be avoided. */
> + /* Do static-pie self relocation for the non-IRELATIVE part after tunables
> + and cpu features are set up. IFUNC entries are deferred until after the
> + TCB and the stack-protector canary are usable, so that an instrumented
> + resolver does not fault. Before this point relocations must be
> + avoided. */
> _dl_relocate_static_pie ();
>
> - /* Perform IREL{,A} relocations. */
> - ARCH_SETUP_IREL ();
> -
> - /* The stack guard goes into the TCB, so initialize it early. */
> + /* Set up the TCB so that the IFUNC pass below can fire resolvers
> + compiled with stack protection, and so that resolvers reading TLS
> + (errno, __thread variables, powerpc's hwcap / at_platform in the
> + TCB) observe an initialised slot. */
> ARCH_SETUP_TLS ();
>
> - /* In some architectures, IREL{,A} relocations happen after TLS setup in
> - order to let IFUNC resolvers benefit from TCB information, e.g. powerpc's
> - hwcap and platform fields available in the TCB. */
> - ARCH_APPLY_IREL ();
> -
> - /* Set up the stack checker's canary. */
> + /* Set up the stack checker's canary. Must happen before any IFUNC resolver
> + runs so a resolver compiled with stack protection loads a defined
> + canary. */
> uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
> # ifdef THREAD_SET_STACK_GUARD
> THREAD_SET_STACK_GUARD (stack_chk_guard);
> @@ -292,10 +291,6 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
> __stack_chk_guard = stack_chk_guard;
> # endif
>
> - /* Initialize libpthread if linked in. */
> - if (__pthread_initialize_minimal != NULL)
> - __pthread_initialize_minimal ();
> -
> /* Set up the pointer guard value. */
> uintptr_t pointer_chk_guard = _dl_setup_pointer_guard (_dl_random,
> stack_chk_guard);
> @@ -305,6 +300,17 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
> __pointer_chk_guard_local = pointer_chk_guard;
> # endif
>
> + /* Now that the TCB, canary, and pointer guard are in place, run the
> + deferred IFUNC relocations. For non-PIE static binaries this is
> + ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
> + phase of _dl_relocate_static_pie above. */
> + _dl_relocate_static_pie_ifunc ();
> + ARCH_SETUP_IREL ();
> +
> + /* Initialize libpthread if linked in. */
> + if (__pthread_initialize_minimal != NULL)
> + __pthread_initialize_minimal ();
> +
> #endif /* !SHARED */
>
> /* Register the destructor of the dynamic linker if there is any. */
> diff --git a/csu/static-reloc.c b/csu/static-reloc.c
> index 2044ca34864..4c3bc7b3270 100644
> --- a/csu/static-reloc.c
> +++ b/csu/static-reloc.c
> @@ -23,4 +23,9 @@ void
> _dl_relocate_static_pie (void)
> {
> }
> +
> +void
> +_dl_relocate_static_pie_ifunc (void)
> +{
> +}
> #endif
> diff --git a/elf/Makefile b/elf/Makefile
> index ff42eb32d41..d7bab52cd97 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -289,8 +289,17 @@ tests-static-internal := \
> tst-tunables-enable_secure \
> # tests-static-internal
>
> +ifeq (yes,$(have-gcc-ifunc))
> +tests-static-internal += \
> + tst-ifunc-resolver-protector-static \
> + tst-ifunc-resolver-protector-static-non-pie \
> + # tests-static-internal
> +endif
> +
> CRT-tst-tls1-static-non-pie := $(csu-objpfx)crt1.o
> tst-tls1-static-non-pie-no-pie = yes
> +CRT-tst-ifunc-resolver-protector-static-non-pie := $(csu-objpfx)crt1.o
> +tst-ifunc-resolver-protector-static-non-pie-no-pie = yes
>
> tests-container := \
> tst-dl-cache-long-path \
> @@ -744,6 +753,19 @@ test-extras += \
> tst-tlsmod17a \
> tst-tlsmod18a \
> # test-extras
> +
> +ifeq (yes,$(have-gcc-ifunc))
> +# The resolver helper needs <tls.h> for the TCB-canary STACK_CHK_GUARD
> +# macro, so it must be compiled with MODULE_NAME=testsuite_internal.
> +extra-test-objs += \
> + tst-ifunc-resolver-protector-static-mod.o \
> + tst-ifunc-resolver-protector-static-non-pie-mod.o \
> + # extra-test-objs
> +test-internal-extras += \
> + tst-ifunc-resolver-protector-static-mod \
> + tst-ifunc-resolver-protector-static-non-pie-mod \
> + # test-internal-extras
> +endif
> modules-names += \
> circlemod1 \
> circlemod1a \
> @@ -1256,7 +1278,11 @@ tests-ifuncstatic := \
> ifuncmain7static \
> # tests-ifuncstatic
> ifeq (yes,$(have-gcc-ifunc))
> -tests-ifuncstatic += ifuncmain9static ifuncmain9picstatic
> +tests-ifuncstatic += \
> + ifuncmain9picstatic \
> + ifuncmain9static \
> + tst-ifunc-bz28817 \
> + # tests-ifuncstatic
> endif
> tests-static += $(tests-ifuncstatic)
> tests-internal += $(tests-ifuncstatic)
> @@ -1268,6 +1294,7 @@ tests += \
> tst-ifunc-plt-bindnow \
> tst-ifunc-plt-dlopen \
> tst-ifunc-plt-dlopen-bindnow \
> + tst-ifunc-resolver-protector \
> tst-ifunc-tls-init \
> tst-ifunc-tls-write \
> # tests
> @@ -1334,6 +1361,7 @@ modules-names += \
> ifuncmod6 \
> tst-ifunc-plt-dep \
> tst-ifunc-plt-lib \
> + tst-ifunc-resolver-protector-mod \
> tst-ifunc-tls-init-lib1 \
> tst-ifunc-tls-init-lib2 \
> tst-ifunc-tls-write-lib \
> @@ -1744,6 +1772,19 @@ $(objpfx)tst-nodelete-opened.out: $(objpfx)tst-nodelete-opened-lib.so
> $(objpfx)tst-tlsalign-extern: $(objpfx)tst-tlsalign-vars.o
> $(objpfx)tst-tlsalign-extern-static: $(objpfx)tst-tlsalign-vars.o
>
> +# The resolver translation unit must always be compiled with
> +# -fstack-protector-all so the canary code is emitted regardless of the
> +# default.
> +CFLAGS-tst-ifunc-resolver-protector-static-mod.c = -fstack-protector-all
> +CFLAGS-tst-ifunc-resolver-protector-static-non-pie-mod.c = -fstack-protector-all
> +CFLAGS-tst-ifunc-resolver-protector-mod.c = -fstack-protector-all
> +$(objpfx)tst-ifunc-resolver-protector-static: \
> + $(objpfx)tst-ifunc-resolver-protector-static-mod.o
> +$(objpfx)tst-ifunc-resolver-protector-static-non-pie: \
> + $(objpfx)tst-ifunc-resolver-protector-static-non-pie-mod.o
> +$(objpfx)tst-ifunc-resolver-protector: \
> + $(objpfx)tst-ifunc-resolver-protector-mod.so
> +
> tst-null-argv-ENV = LD_DEBUG=all LD_DEBUG_OUTPUT=$(objpfx)tst-null-argv.debug.out
> LDFLAGS-nodel2mod3.so = -Wl,--no-as-needed
> LDFLAGS-reldepmod5.so = -Wl,--no-as-needed
> diff --git a/elf/dl-reloc-static-pie.c b/elf/dl-reloc-static-pie.c
> index 63ce609024e..8463e46147d 100644
> --- a/elf/dl-reloc-static-pie.c
> +++ b/elf/dl-reloc-static-pie.c
> @@ -31,8 +31,10 @@
> #include "dynamic-link.h"
> #include "get-dynamic-info.h"
>
> -/* Relocate static executable with PIE. */
> -
> +/* Phase 1: relocate static PIE - non-IRELATIVE pass. IFUNC resolvers are
> + deferred to _dl_relocate_static_pie_ifunc so that csu/libc-start.c can
> + initialise the TCB (and write the stack-protector canary into it) between
> + the two passes. */
> void
> _dl_relocate_static_pie (void)
> {
> @@ -76,10 +78,9 @@ _dl_relocate_static_pie (void)
> ELF_MACHINE_BEFORE_RTLD_RELOC (main_map, main_map->l_info);
> # endif
>
> - /* Relocate ourselves so we can do normal function calls and
> - data access using the global offset table. */
> - ELF_DYNAMIC_RELOCATE (main_map, NULL, 0, 0, 0);
> - main_map->l_relocated = 1;
> + /* Relocate ourselves so we can do normal function calls and data access
> + using the global offset table. IRELATIVE entries are deferred. */
> + ELF_DYNAMIC_RELOCATE_NOIFUNC (main_map, NULL, 0, 0);
>
> /* Initialize _r_debug_extended. */
> struct r_debug *r = _dl_debug_initialize (0, LM_ID_BASE);
> @@ -89,4 +90,15 @@ _dl_relocate_static_pie (void)
> time. */
> elf_setup_debug_entry (main_map, r);
> }
> +
> +/* Phase 2: run the deferred IRELATIVE entries for the static-pie main map.
> + Must be called after the TCB is set up and the stack-protector canary is
> + written, so that an instrumented IFUNC resolver does not fault. */
> +void
> +_dl_relocate_static_pie_ifunc (void)
> +{
> + struct link_map *main_map = _dl_get_dl_main_map ();
> + ELF_DYNAMIC_RELOCATE_IFUNC (main_map, NULL, 0, 0);
> + main_map->l_relocated = 1;
> +}
> #endif
> diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h
> index 334e241f63c..35141acec4b 100644
> --- a/elf/dynamic-link.h
> +++ b/elf/dynamic-link.h
> @@ -78,8 +78,18 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
> consumes precisely the very end of the DT_REL*, or DT_JMPREL and DT_REL*
> are completely separate and there is a gap between them. */
>
> +/* This controls which sub-passes _ELF_DYNAMIC_DO_RELOC runs. Used to
> + interleave TLS / stack-protector setup between the two passes so IFUNC
> + resolvers see a fully-initialised TCB. */
> +enum elf_dynamic_reloc_phase
> +{
> + DL_RELOC_BOTH = 0, /* Non-IRELATIVE pass then IRELATIVE pass. */
> + DL_RELOC_NOIFUNC = 1, /* Non-IRELATIVE pass only. */
> + DL_RELOC_IFUNC = 2, /* IRELATIVE pass only. */
> +};
> +
> # define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, \
> - test_rel) \
> + test_rel, phase) \
> do { \
> struct { ElfW(Addr) start, size; \
> __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; } \
> @@ -126,19 +136,21 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
> by the linker. */ \
> if (!DO_RTLD_BOOTSTRAP) \
> { \
> - for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
> - elf_dynamic_do_##reloc ((map), scope, \
> - ranges[ranges_index].start, \
> - ranges[ranges_index].size, \
> - ranges[ranges_index].nrelative, \
> - ranges[ranges_index].lazy); \
> - for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
> - elf_dynamic_do_##reloc##_irelative ((map), scope, \
> - ranges[ranges_index].start, \
> - ranges[ranges_index].size, \
> - ranges[ranges_index].nrelative,\
> - ranges[ranges_index].lazy, \
> - skip_ifunc); \
> + if ((phase) != DL_RELOC_IFUNC) \
> + for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
> + elf_dynamic_do_##reloc ((map), scope, \
> + ranges[ranges_index].start, \
> + ranges[ranges_index].size, \
> + ranges[ranges_index].nrelative, \
> + ranges[ranges_index].lazy); \
> + if ((phase) != DL_RELOC_NOIFUNC) \
> + for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
> + elf_dynamic_do_##reloc##_irelative ((map), scope, \
> + ranges[ranges_index].start, \
> + ranges[ranges_index].size, \
> + ranges[ranges_index].nrelative,\
> + ranges[ranges_index].lazy, \
> + skip_ifunc); \
> } \
> else \
> for (int ranges_index = 0; ranges_index < 2; ++ranges_index) \
> @@ -158,18 +170,36 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
> # if ! ELF_MACHINE_NO_REL
> # include "do-rel.h"
> # define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc) \
> - _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, _ELF_CHECK_REL)
> + _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, \
> + _ELF_CHECK_REL, DL_RELOC_BOTH)
> +# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy) \
> + _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, 0, \
> + _ELF_CHECK_REL, DL_RELOC_NOIFUNC)
> +# define ELF_DYNAMIC_DO_REL_IFUNCONLY(map, scope, lazy, skip_ifunc) \
> + _ELF_DYNAMIC_DO_RELOC (REL, Rel, map, scope, lazy, skip_ifunc, \
> + _ELF_CHECK_REL, DL_RELOC_IFUNC)
> # else
> # define ELF_DYNAMIC_DO_REL(map, scope, lazy, skip_ifunc) /* Nothing to do. */
> +# define ELF_DYNAMIC_DO_REL_NOIFUNC(map, scope, lazy) /* Nothing to do. */
> +# define ELF_DYNAMIC_DO_REL_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
> # endif
>
> # if ! ELF_MACHINE_NO_RELA
> # define DO_RELA
> # include "do-rel.h"
> # define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc) \
> - _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, _ELF_CHECK_REL)
> + _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, \
> + _ELF_CHECK_REL, DL_RELOC_BOTH)
> +# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy) \
> + _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, 0, \
> + _ELF_CHECK_REL, DL_RELOC_NOIFUNC)
> +# define ELF_DYNAMIC_DO_RELA_IFUNCONLY(map, scope, lazy, skip_ifunc) \
> + _ELF_DYNAMIC_DO_RELOC (RELA, Rela, map, scope, lazy, skip_ifunc, \
> + _ELF_CHECK_REL, DL_RELOC_IFUNC)
> # else
> # define ELF_DYNAMIC_DO_RELA(map, scope, lazy, skip_ifunc) /* Nothing to do. */
> +# define ELF_DYNAMIC_DO_RELA_NOIFUNC(map, scope, lazy) /* Nothing to do. */
> +# define ELF_DYNAMIC_DO_RELA_IFUNCONLY(map, scope, lazy, skip_ifunc) /* Nothing. */
> # endif
>
> # define ELF_DYNAMIC_DO_RELR(map) \
> @@ -221,4 +251,26 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[],
> ELF_DYNAMIC_AFTER_RELOC ((map), (edr_lazy)); \
> } while (0)
>
> +/* Like ELF_DYNAMIC_RELOCATE but only processes the non-IRELATIVE pass.
> + The IRELATIVE pass must be completed later via ELF_DYNAMIC_RELOCATE_IFUNC.
> + Used by the static-pie startup so the TCB and stack-protector canary can
> + be initialised between the two passes. */
> +# define ELF_DYNAMIC_RELOCATE_NOIFUNC(map, scope, lazy, consider_profile) \
> + do { \
> + int edr_lazy = elf_machine_runtime_setup ((map), (scope), (lazy), \
> + (consider_profile)); \
> + if (!is_rtld_link_map (map) || DO_RTLD_BOOTSTRAP) \
> + ELF_DYNAMIC_DO_RELR (map); \
> + ELF_DYNAMIC_DO_REL_NOIFUNC ((map), (scope), edr_lazy); \
> + ELF_DYNAMIC_DO_RELA_NOIFUNC ((map), (scope), edr_lazy); \
> + ELF_DYNAMIC_AFTER_RELOC ((map), (edr_lazy)); \
> + } while (0)
> +
> +/* IRELATIVE-only companion to ELF_DYNAMIC_RELOCATE_NOIFUNC. */
> +# define ELF_DYNAMIC_RELOCATE_IFUNC(map, scope, lazy, skip_ifunc) \
> + do { \
> + ELF_DYNAMIC_DO_REL_IFUNCONLY ((map), (scope), (lazy), skip_ifunc); \
> + ELF_DYNAMIC_DO_RELA_IFUNCONLY ((map), (scope), (lazy), skip_ifunc); \
> + } while (0)
> +
> #endif
> diff --git a/elf/tst-ifunc-bz28817.c b/elf/tst-ifunc-bz28817.c
> new file mode 100644
> index 00000000000..fe6f400f4ea
> --- /dev/null
> +++ b/elf/tst-ifunc-bz28817.c
> @@ -0,0 +1,60 @@
> +/* BZ 28817: TLS read from a static-pie IFUNC resolver.
> + 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 <stdio.h>
> +#include <stdlib.h>
> +#include <support/check.h>
> +
> +__thread int bar;
> +extern __thread int bar_gd asm ("bar")
> + __attribute__ ((tls_model("global-dynamic")));
> +static int *bar_ptr;
> +
> +int foo (void);
> +
> +static void
> +init_foo (void)
> +{
> + bar_ptr = &bar_gd;
> +}
> +
> +static int
> +my_foo (void)
> +{
> + return bar_ptr != NULL;
> +}
> +
> +static __typeof (foo) *
> +inhibit_stack_protector
> +foo_ifunc (void)
> +{
> + init_foo ();
> + __typeof (foo) *res = my_foo;
> + return res;
> +};
> +__typeof (foo) foo __attribute__ ((ifunc ("foo" "_ifunc")));
> +
> +static int
> +do_test (void)
> +{
> + TEST_VERIFY (foo ());
> + TEST_VERIFY (&bar == bar_ptr);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/elf/tst-ifunc-resolver-protector-mod.c b/elf/tst-ifunc-resolver-protector-mod.c
> new file mode 100644
> index 00000000000..36d11f04def
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector-mod.c
> @@ -0,0 +1,61 @@
> +/* Stack-protector-instrumented IFUNC resolver in a shared library, used
> + by tst-ifunc-resolver-protector. BZ #27582.
> + 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/>. */
> +
> +/* Built with -fstack-protector-all so the resolver's prologue/epilogue
> + carries the full canary check. Reaching the return statement means the
> + canary load did not fault (TCB-canary ABIs) and the canary compare did not
> + call __stack_chk_fail. The dynamic linker calls security_init() before the
> + main relocation loop, so resolvers fired from startup or dlopen should
> + always observe an initialised canary; this test guards against any future
> + reordering that would break that invariant. */
> +
> +#define SENTINEL 0x5A5A1234
> +
> +static volatile int resolver_ran;
> +
> +int
> +get_resolver_ran (void)
> +{
> + return resolver_ran;
> +}
> +
> +static int
> +impl_ok (int x)
> +{
> + return x + SENTINEL;
> +}
> +
> +typedef int (*fn_t) (int);
> +
> +static fn_t
> +resolver (void)
> +{
> + /* Buffer + zero-fill force -fstack-protector-all canary code. */
> + volatile char buf[32];
> + for (unsigned i = 0; i < sizeof (buf); ++i)
> + buf[i] = 0;
> + resolver_ran = 1;
> + return impl_ok;
> +}
> +
> +int compute (int) __attribute__ ((ifunc ("resolver")));
> +
> +/* Address taken in DSO data to force an R_*_IRELATIVE in .rela.dyn (a
> + non-PLT relocation against the IFUNC). */
> +int (*fptr) (int) = compute;
> diff --git a/elf/tst-ifunc-resolver-protector-static-mod.c b/elf/tst-ifunc-resolver-protector-static-mod.c
> new file mode 100644
> index 00000000000..b1cb704854e
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector-static-mod.c
> @@ -0,0 +1,68 @@
> +/* Stack-protector-instrumented IFUNC resolver used by
> + tst-ifunc-resolver-protector-static. BZ #34164.
> + 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 translation unit is built with -fstack-protector-all so that the
> + compiler instruments the resolver below. The resolver's prologue loads
> + the canary from the TCB (TCB-canary ABIs) or from __stack_chk_guard
> + (global-var ABIs); the epilogue compares it against the on-stack copy.
> + Pre-fix the prologue load either faulted (TCB-canary) or loaded zero
> + (global-var).
> +
> + The resolver also records the canary value via STACK_CHK_GUARD so the
> + test can verify it matches what main observes. */
> +
> +#include <stackguard-macros.h>
> +#include <stdint.h>
> +#include <tls.h>
> +
> +#define SENTINEL 0x5A5A1234
> +
> +static volatile uintptr_t resolver_canary;
> +
> +uintptr_t
> +get_resolver_canary (void)
> +{
> + return resolver_canary;
> +}
> +
> +static int
> +impl_ok (int x)
> +{
> + return x + SENTINEL;
> +}
> +
> +typedef int (*fn_t) (int);
> +
> +static fn_t
> +resolver (void)
> +{
> + /* Buffer forces -fstack-protector-all to emit canary code even with
> + no other reason to. */
> + volatile char buf[32];
> + for (unsigned i = 0; i < sizeof (buf); ++i)
> + buf[i] = 0;
> +
> + resolver_canary = STACK_CHK_GUARD;
> +
> + return impl_ok;
> +}
> +
> +int compute (int) __attribute__ ((ifunc ("resolver")));
> +
> +int (*fptr) (int) = compute;
> diff --git a/elf/tst-ifunc-resolver-protector-static-non-pie-mod.c b/elf/tst-ifunc-resolver-protector-static-non-pie-mod.c
> new file mode 100644
> index 00000000000..a5d200c0662
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector-static-non-pie-mod.c
> @@ -0,0 +1,2 @@
> +/* Companion module for tst-ifunc-resolver-protector-static-non-pie. */
> +#include "tst-ifunc-resolver-protector-static-mod.c"
> diff --git a/elf/tst-ifunc-resolver-protector-static-non-pie.c b/elf/tst-ifunc-resolver-protector-static-non-pie.c
> new file mode 100644
> index 00000000000..2f42e91f3b6
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector-static-non-pie.c
> @@ -0,0 +1,5 @@
> +/* Same coverage as tst-ifunc-resolver-protector-static, but linked as non-PIE
> + static. Exercises the apply_irel / __rela_iplt_start path instead of the
> + static-pie _dl_relocate_static_pie_ifunc path. */
> +
> +#include "tst-ifunc-resolver-protector-static.c"
> diff --git a/elf/tst-ifunc-resolver-protector-static.c b/elf/tst-ifunc-resolver-protector-static.c
> new file mode 100644
> index 00000000000..e594e1c2653
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector-static.c
> @@ -0,0 +1,61 @@
> +/* Check that a stack-protector-instrumented IFUNC resolver works in a
> + static binary. BZ #34164.
> + 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/>. */
> +
> +/* When a static binary's IFUNC resolver is compiled with stack protector, the
> + resolver prologue loads the canary from the TCB (TCB-canary ABIs: x86_64,
> + i386, powerpc, s390) or from the global __stack_chk_guard (other ABIs).
> + The test checks if TCB-canary or the global-var is properly initialized:
> +
> + 1. The resolver runs without SIGSEGV (TCB-canary) or SIGABRT (canary
> + mismatch).
> +
> + 2. The resolver records the canary value it observed via STACK_CHK_GUARD,
> + and main asserts it equals the value visible from user code -- catching
> + the "silent zero canary" variant on global-var ABIs. */
> +
> +#include <stackguard-macros.h>
> +#include <stdint.h>
> +#include <tls.h>
> +#include <support/check.h>
> +
> +#define SENTINEL 0x5A5A1234
> +
> +extern int compute (int);
> +extern uintptr_t get_resolver_canary (void);
> +
> +static int
> +do_test (void)
> +{
> + /* compute() returns its argument + SENTINEL iff the resolver picked
> + impl_ok, which it does whenever the canary check at the resolver
> + prologue / epilogue did not abort. */
> + TEST_COMPARE (compute (1), 1 + SENTINEL);
> +
> + /* Silent-variant check: the canary the resolver loaded must match the
> + canary do_test reads. On global-var ABIs, it checks if the stack
> + protector cookie is properly initialised. */
> + uintptr_t resolver_canary = get_resolver_canary ();
> + uintptr_t main_canary = STACK_CHK_GUARD;
> + TEST_VERIFY (resolver_canary != 0);
> + TEST_COMPARE (resolver_canary, main_canary);
> +
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/elf/tst-ifunc-resolver-protector.c b/elf/tst-ifunc-resolver-protector.c
> new file mode 100644
> index 00000000000..3e085e55091
> --- /dev/null
> +++ b/elf/tst-ifunc-resolver-protector.c
> @@ -0,0 +1,42 @@
> +/* Check that a stack-protector-instrumented IFUNC resolver works in a
> + dynamically-linked binary. BZ #27582.
> + 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/>. */
> +
> +/* Dynamic counterpart to tst-ifunc-resolver-protector-static.
> +
> + The dynamic linker calls security_init() before the main relocation
> + loop, so a stack-protected resolver fired from startup should observe
> + an initialised canary -- but having a regression test guards against
> + any future reordering that would break that invariant. */
> +
> +#include <support/check.h>
> +
> +#define SENTINEL 0x5A5A1234
> +
> +extern int compute (int);
> +extern int get_resolver_ran (void);
> +
> +static int
> +do_test (void)
> +{
> + TEST_COMPARE (compute (1), 1 + SENTINEL);
> + TEST_VERIFY (get_resolver_ran () != 0);
> + return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
> index 9f772614bf6..26fcd1977ea 100644
> --- a/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h
> @@ -21,5 +21,9 @@
>
> asm ("memset = __memset_generic");
> asm ("strlen = __strlen_generic");
> +#ifndef SHARED
> +asm ("memcpy = __memcpy_generic");
> +asm ("memmove = __memmove_generic");
> +#endif
>
> #endif
> diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
> index 15c46598539..24529db8a1e 100644
> --- a/sysdeps/generic/ldsodefs.h
> +++ b/sysdeps/generic/ldsodefs.h
> @@ -1197,10 +1197,15 @@ void __tls_init_tp (void) attribute_hidden;
> void __libc_setup_tls (void);
>
> # if ENABLE_STATIC_PIE
> -/* Relocate static executable with PIE. */
> +/* _dl_relocate_static_pie runs every relocation except IRELATIVE. The
> + second entry point _dl_relocate_static_pie_ifunc must be invoked
> + afterwards -- but only once the TCB and the stack-protector canary
> + are usable -- to fire the IFUNC resolvers. */
> extern void _dl_relocate_static_pie (void) attribute_hidden;
> +extern void _dl_relocate_static_pie_ifunc (void) attribute_hidden;
> # else
> # define _dl_relocate_static_pie()
> +# define _dl_relocate_static_pie_ifunc()
> # endif
> #endif
>
> diff --git a/sysdeps/generic/libc-start.h b/sysdeps/generic/libc-start.h
> index a36f1fa71d5..8896198fb2e 100644
> --- a/sysdeps/generic/libc-start.h
> +++ b/sysdeps/generic/libc-start.h
> @@ -20,12 +20,11 @@
> #define _LIBC_START_H
>
> #ifndef SHARED
> -/* By default we perform STT_GNU_IFUNC resolution *before* TLS
> - initialization, and this means you cannot, without machine
> - knowledge, access TLS from an IFUNC resolver. */
> +/* Static startup runs ARCH_SETUP_TLS (and writes the stack-protector
> + canary) before ARCH_SETUP_IREL, so IFUNC resolvers can read TLS and
> + stack-protector instrumented resolvers do not fault. */
> +#define ARCH_SETUP_TLS() __libc_setup_tls ()
> #define ARCH_SETUP_IREL() apply_irel ()
> -#define ARCH_SETUP_TLS() __libc_setup_tls ()
> -#define ARCH_APPLY_IREL()
> #endif /* ! SHARED */
>
> #endif /* _LIBC_START_H */
> diff --git a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> index 8af00d2846b..1cd204c2f69 100644
> --- a/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/loongarch/lp64/multiarch/dl-symbol-redir-ifunc.h
> @@ -23,6 +23,8 @@
> asm ("memset = __memset_aligned");
> asm ("memcmp = __memcmp_aligned");
> asm ("strlen = __strlen_aligned");
> +asm ("memcpy = __memcpy_unaligned");
> +asm ("memmove = __memmove_unaligned");
> #endif
>
> #endif
> diff --git a/sysdeps/unix/sysv/linux/aarch64/libc-start.h b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> index 4ccd13741bc..53683ee511b 100644
> --- a/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> +++ b/sysdeps/unix/sysv/linux/aarch64/libc-start.h
> @@ -76,7 +76,6 @@ aarch64_libc_setup_tls (void)
>
> # define ARCH_SETUP_IREL() apply_irel ()
> # define ARCH_SETUP_TLS() aarch64_libc_setup_tls ()
> -# define ARCH_APPLY_IREL()
> #endif /* ! SHARED */
>
> #endif /* _LIBC_START_H */
> diff --git a/sysdeps/unix/sysv/linux/powerpc/libc-start.h b/sysdeps/unix/sysv/linux/powerpc/libc-start.h
> deleted file mode 100644
> index 67c361a83c1..00000000000
> --- a/sysdeps/unix/sysv/linux/powerpc/libc-start.h
> +++ /dev/null
> @@ -1,30 +0,0 @@
> -/* PowerPC definitions for libc main startup.
> - Copyright (C) 2017-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/>. */
> -
> -#ifndef _LIBC_START_H
> -#define _LIBC_START_H
> -
> -#ifndef SHARED
> -/* IREL{,A} must happen after TCB initialization in order to allow IFUNC
> - resolvers to read TCB fields, e.g. hwcap and at_platform. */
> -#define ARCH_SETUP_IREL()
> -#define ARCH_SETUP_TLS() __libc_setup_tls ()
> -#define ARCH_APPLY_IREL() apply_irel ()
> -#endif /* ! SHARED */
> -
> -#endif /* _LIBC_START_H */
> diff --git a/sysdeps/x86_64/libc-start.h b/sysdeps/x86_64/libc-start.h
> index 7e38fdef873..036e0e472a8 100644
> --- a/sysdeps/x86_64/libc-start.h
> +++ b/sysdeps/x86_64/libc-start.h
> @@ -18,7 +18,6 @@
>
> #ifndef SHARED
> # define ARCH_SETUP_IREL() apply_irel ()
> -# define ARCH_APPLY_IREL()
> # ifdef __CET__
> /* Get CET features enabled in the static executable. */
>
> diff --git a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> index b607e525f2c..1f3ca20307c 100644
> --- a/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> +++ b/sysdeps/x86_64/multiarch/dl-symbol-redir-ifunc.h
> @@ -54,6 +54,25 @@ asm ("memcmp = " HAVE_MEMCMP_IFUNC_GENERIC);
>
> asm ("strlen = " HAVE_STRCMP_IFUNC_GENERIC);
>
> +#if MINIMUM_X86_ISA_LEVEL >= 4
> +# define HAVE_MEMCPY_IFUNC_GENERIC "__memcpy_evex_unaligned"
> +# define HAVE_MEMMOVE_IFUNC_GENERIC "__memmove_evex_unaligned"
> +# define HAVE_MEMPCPY_IFUNC_GENERIC "__mempcpy_evex_unaligned"
> +#elif MINIMUM_X86_ISA_LEVEL == 3
> +# define HAVE_MEMCPY_IFUNC_GENERIC "__memcpy_avx_unaligned"
> +# define HAVE_MEMMOVE_IFUNC_GENERIC "__memmove_avx_unaligned"
> +# define HAVE_MEMPCPY_IFUNC_GENERIC "__mempcpy_avx_unaligned"
> +#else
> +# define HAVE_MEMCPY_IFUNC_GENERIC "__memcpy_sse2_unaligned"
> +# define HAVE_MEMMOVE_IFUNC_GENERIC "__memmove_sse2_unaligned"
> +# define HAVE_MEMPCPY_IFUNC_GENERIC "__mempcpy_sse2_unaligned"
> +#endif
> +
> +asm ("memcpy = " HAVE_MEMCPY_IFUNC_GENERIC);
> +asm ("memmove = " HAVE_MEMMOVE_IFUNC_GENERIC);
> +asm ("mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
> +asm ("__mempcpy = " HAVE_MEMPCPY_IFUNC_GENERIC);
> +
> #endif /* SHARED */
>
> #endif
> --
> 2.43.0
>
LGTM.
Reviewed-by: H.J. Lu <hjl.tools@gmail.com>
Thanks.
--
H.J.
More information about the Libc-alpha
mailing list