From: Adhemerval Zanella Date: Fri, 22 May 2026 17:08:13 +0000 (-0300) Subject: elf: Initialize TCB and stack-protector before static IFUNC resolvers (BZ 20680,... X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b75ad99d45b;p=thirdparty%2Fglibc.git elf: Initialize TCB and stack-protector before static IFUNC resolvers (BZ 20680, BZ 27582, BZ 28817) 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. Reviewed-by: H.J. Lu --- diff --git a/csu/libc-start.c b/csu/libc-start.c index 1c58561bce..03d770ef15 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 2044ca3486..4c3bc7b327 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 ff42eb32d4..d7bab52cd9 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 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 63ce609024..8463e46147 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 334e241f63..35141acec4 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 0000000000..fe6f400f4e --- /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 + . */ + +#include +#include +#include + +__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 diff --git a/elf/tst-ifunc-resolver-protector-mod.c b/elf/tst-ifunc-resolver-protector-mod.c new file mode 100644 index 0000000000..36d11f04de --- /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 + . */ + +/* 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 0000000000..b1cb704854 --- /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 + . */ + +/* 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 +#include +#include + +#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 0000000000..a5d200c066 --- /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 0000000000..2f42e91f3b --- /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 0000000000..e594e1c265 --- /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 + . */ + +/* 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 +#include +#include +#include + +#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 diff --git a/elf/tst-ifunc-resolver-protector.c b/elf/tst-ifunc-resolver-protector.c new file mode 100644 index 0000000000..3e085e5509 --- /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 + . */ + +/* 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 + +#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 diff --git a/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h b/sysdeps/aarch64/multiarch/dl-symbol-redir-ifunc.h index 9f772614bf..26fcd1977e 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 15c4659853..24529db8a1 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 a36f1fa71d..8896198fb2 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 8af00d2846..1cd204c2f6 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 4ccd13741b..53683ee511 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 67c361a83c..0000000000 --- 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 - . */ - -#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 7e38fdef87..036e0e472a 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 b607e525f2..1f3ca20307 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