[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