| Message ID | 20260514142803.4093833-1-adhemerval.zanella@linaro.org |
|---|---|
| State | New |
| Headers | show |
| Series | [v2] elf: Defer all IRELATIVE relocations until after PLT setup | expand |
On Thu, May 14, 2026 at 10:28 PM Adhemerval Zanella <adhemerval.zanella@linaro.org> wrote: > > When a shared library is built with -z lazy and its IFUNC resolver calls > a PLT function, the dynamic linker can crash. The resolver runs while > the PLT stubs still hold their raw ELF virtual addresses — l_addr has > not yet been added — so the call branches to an unmapped address. > > The old code deferred IRELATIVE entries only to the end of the relocation > range currently being processed (via the r2/end2 scan-ahead mechanism in > elf_dynamic_do_Rel). This was sufficient only when both IRELATIVE and the > JMP_SLOT entries for the PLT functions it needs are in the same section. > On x86-64, aarch64, arm, i386 and most other targets, a file-scope > initialiser of the form > > int (*fptr)(void) = some_ifunc; > > causes the linker to place R_*_IRELATIVE in .rela.dyn, while JMP_SLOT > entries for any PLT calls made by the resolver live in .rela.plt. > Processing .rela.dyn before .rela.plt means the resolver fires before the > PLT is usable, regardless of where within .rela.dyn IRELATIVE appears. > > Fix this by splitting IRELATIVE processing into a separate, explicitly > deferred pass. In elf/do-rel.h: > > - Remove the r2/end2 variables and the post-loop IRELATIVE re-scan from > elf_dynamic_do_Rel. IRELATIVE entries are now always skipped in the > non-bootstrap path. > > - Add a new elf_dynamic_do_Rel_irelative function that scans a > relocation range and calls elf_machine_rel/elf_machine_lazy_rel for > IRELATIVE and ifunc relocations. > > In elf/dynamic-link.h, update _ELF_DYNAMIC_DO_RELOC to use a two-phase > approach for non-bootstrap builds unconditionally (regardless of whether > ranges[1].size is zero): > > Phase 1+2: elf_dynamic_do_Rel over .rela.dyn then .rela.plt — processes > everything except IRELATIVE/STT_GNU_IFUNC. > Phase 3+4: elf_dynamic_do_Rel_irelative over .rela.dyn then .rela.plt — > processes only IRELATIVE, by which point all PLT stubs are > valid. > > This guarantees that IRELATIVE resolvers can call PLT stubs safely > regardless of which section the linker placed R_*_IRELATIVE in. > > Add ELF_MACHINE_IRELATIVE to the architectures that were missing it so > the new skip logic in elf_dynamic_do_Rel is compiled for all targets. > > This patch addresses the binutils BZ 13302 [1] from the glibc side, and > also fixes the mold-reported issue [2], which shows that IFUNC relocation > placement and processing can work differently across ABIs. > > I checked on all ABIs that support IFUNC (x86_64, i686, aarch64, arm, > loongarch, powerpc, riscv, s390, and sparc), some via qemu-system. > > [1] https://sourceware.org/bugzilla/show_bug.cgi?id=13302 > [2] https://github.com/rui314/mold/issues/1550 > -- > Changes from v1: > - Issue the la_symbind for JMP_SLOT entries that target a defined > STT_GNU_IFUNC symbol. > > - elf/do-rel.h: pass nrelative through to elf_dynamic_do_Rel_irelative > and skip the leading R_*_RELATIVE block without filter inspection > (those entries never match the IRELATIVE/STT_GNU_IFUNC predicate). > > - elf/do-rel.h: add the missing #undef for elf_dynamic_is_Rel_irelative > and elf_dynamic_Rel_audit_symbind. > > - Add elf/tst-ifunc-plt-bindnow.c and elf/tst-ifunc-plt-dlopen-bindnow.c > test: LD_BIND_NOW=1 for the startup-load variant, RTLD_NOW for the > dlopen variant. These exercise the elf_machine_rel path through > elf_dynamic_do_Rel_irelative that the lazy tests do not. > --- > elf/Makefile | 20 +++ > elf/do-rel.h | 234 +++++++++++++++---------- > elf/dynamic-link.h | 38 +++- > elf/tst-ifunc-plt-bindnow.c | 24 +++ > elf/tst-ifunc-plt-dep.c | 23 +++ > elf/tst-ifunc-plt-dlopen-bindnow.c | 45 +++++ > elf/tst-ifunc-plt-dlopen.c | 46 +++++ > elf/tst-ifunc-plt-lib.c | 56 ++++++ > elf/tst-ifunc-plt.c | 38 ++++ > sysdeps/aarch64/dl-machine.h | 1 + > sysdeps/arm/dl-machine.h | 1 + > sysdeps/i386/dl-machine.h | 1 + > sysdeps/powerpc/powerpc32/dl-machine.h | 1 + > sysdeps/powerpc/powerpc64/dl-machine.h | 1 + > sysdeps/riscv/dl-machine.h | 1 + > sysdeps/sparc/sparc32/dl-machine.h | 1 + > sysdeps/sparc/sparc64/dl-machine.h | 1 + > 17 files changed, 428 insertions(+), 104 deletions(-) > create mode 100644 elf/tst-ifunc-plt-bindnow.c > create mode 100644 elf/tst-ifunc-plt-dep.c > create mode 100644 elf/tst-ifunc-plt-dlopen-bindnow.c > create mode 100644 elf/tst-ifunc-plt-dlopen.c > create mode 100644 elf/tst-ifunc-plt-lib.c > create mode 100644 elf/tst-ifunc-plt.c > > diff --git a/elf/Makefile b/elf/Makefile > index f4d22c15991..0f888887be7 100644 > --- a/elf/Makefile > +++ b/elf/Makefile > @@ -1255,6 +1255,10 @@ ifeq (yes,$(build-shared)) > tests += \ > tst-ifunc-fault-bindnow \ > tst-ifunc-fault-lazy \ > + tst-ifunc-plt \ > + tst-ifunc-plt-bindnow \ > + tst-ifunc-plt-dlopen \ > + tst-ifunc-plt-dlopen-bindnow \ > # tests > # Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8. > tests-internal += \ > @@ -1317,6 +1321,8 @@ modules-names += \ > ifuncmod1 \ > ifuncmod3 \ > ifuncmod6 \ > + tst-ifunc-plt-dep \ > + tst-ifunc-plt-lib \ > # modules-names > ifeq (no,$(with-lld)) > modules-names += ifuncmod5 > @@ -1796,6 +1802,7 @@ unload4mod1.so-no-z-defs = yes > ifuncmod1.so-no-z-defs = yes > ifuncmod5.so-no-z-defs = yes > ifuncmod6.so-no-z-defs = yes > +tst-ifunc-plt-lib.so-no-z-defs = yes > tst-auditmod9a.so-no-z-defs = yes > tst-auditmod9b.so-no-z-defs = yes > tst-nodelete-uniquemod.so-no-z-defs = yes > @@ -2429,6 +2436,19 @@ $(objpfx)tst-ifunc-fault-bindnow.out: $(objpfx)tst-ifunc-fault-bindnow \ > $(objpfx)ld.so > $(tst-ifunc-fault-script) > > +LDFLAGS-tst-ifunc-plt-lib.so = -Wl,-z,lazy > + > +tst-ifunc-plt-bindnow-ENV = LD_BIND_NOW=1 > +tst-ifunc-plt-dlopen-bindnow-ENV = LD_BIND_NOW=1 > + > +$(objpfx)tst-ifunc-plt-lib.so: $(objpfx)tst-ifunc-plt-dep.so > +$(objpfx)tst-ifunc-plt: $(objpfx)tst-ifunc-plt-lib.so > +$(objpfx)tst-ifunc-plt-bindnow: $(objpfx)tst-ifunc-plt-lib.so > +$(objpfx)tst-ifunc-plt-dlopen.out: \ > + $(objpfx)tst-ifunc-plt-lib.so $(objpfx)tst-ifunc-plt-dep.so > +$(objpfx)tst-ifunc-plt-dlopen-bindnow.out: \ > + $(objpfx)tst-ifunc-plt-lib.so $(objpfx)tst-ifunc-plt-dep.so > + > $(objpfx)tst-unique1.out: $(objpfx)tst-unique1mod1.so \ > $(objpfx)tst-unique1mod2.so > > diff --git a/elf/do-rel.h b/elf/do-rel.h > index d00ffab7e96..c7a8be368f0 100644 > --- a/elf/do-rel.h > +++ b/elf/do-rel.h > @@ -23,6 +23,9 @@ > > #ifdef DO_RELA > # define elf_dynamic_do_Rel elf_dynamic_do_Rela > +# define elf_dynamic_do_Rel_irelative elf_dynamic_do_Rela_irelative > +# define elf_dynamic_is_Rel_irelative elf_dynamic_is_Rela_irelative > +# define elf_dynamic_Rel_audit_symbind elf_dynamic_Rela_audit_symbind > # define Rel Rela > # define elf_machine_rel elf_machine_rela > # define elf_machine_rel_relative elf_machine_rela_relative > @@ -34,16 +37,53 @@ > (void *) (l_addr + relative->r_offset)) > #endif > > +static __always_inline bool > +elf_dynamic_is_Rel_irelative (const ElfW(Rel) *reloc, const ElfW(Sym) *sym) > +{ > +#ifdef ELF_MACHINE_IRELATIVE > + const unsigned int r_type = ELFW (R_TYPE) (reloc->r_info); > + return ((sym != NULL > + && ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC > + && sym->st_shndx != SHN_UNDEF) > + || r_type == ELF_MACHINE_IRELATIVE); > +#else > + return false; > +#endif > +} > + > +static __always_inline void > +elf_dynamic_Rel_audit_symbind (struct link_map *map, > + struct r_scope_elem *scope[], > + const ElfW(Rel) *reloc, const ElfW(Sym) *sym, > + const struct r_found_version *rversion, > + void *r_addr_arg) > +{ > +#if defined SHARED > + if (ELFW(R_TYPE) (reloc->r_info) == ELF_MACHINE_JMP_SLOT > + && GLRO(dl_naudit) > 0) > + { > + struct link_map *sym_map > + = RESOLVE_MAP (map, scope, &sym, rversion, ELF_MACHINE_JMP_SLOT); > + if (sym != NULL) > + _dl_audit_symbind (map, NULL, reloc, sym, r_addr_arg, sym_map, false); > + } > +#endif > +} > + > /* Perform the relocations in MAP on the running program image as specified > by RELTAG, SZTAG. If LAZY is nonzero, this is the first pass on PLT > relocations; they should be set up to call _dl_runtime_resolve, rather > - than fully resolved now. */ > + than fully resolved now. > + > + IRELATIVE entries are always skipped (non-bootstrap); they are handled > + separately by elf_dynamic_do_Rel_irelative after all other relocations > + for both .rel.dyn and .rel.plt have been processed. */ > > static inline void __attribute__ ((always_inline)) > elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], > ElfW(Addr) reladdr, ElfW(Addr) relsize, > __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative, > - int lazy, int skip_ifunc) > + int lazy) > { > const ElfW(Rel) *relative = (const void *) reladdr; > const ElfW(Rel) *r = relative + nrelative; > @@ -65,14 +105,9 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], > void *const r_addr_arg = (void *) (l_addr + r->r_offset); > const struct r_found_version *rversion = &map->l_versions[ndx]; > > - elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, skip_ifunc); > + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0); > } > #else /* !RTLD_BOOTSTRAP */ > -# if defined ELF_MACHINE_IRELATIVE > - const ElfW(Rel) *r2 = NULL; > - const ElfW(Rel) *end2 = NULL; > -# endif > - > #if !defined DO_RELA || !defined ELF_MACHINE_PLT_REL > /* We never bind lazily during ld.so bootstrap. Unfortunately gcc is > not clever enough to see through all the function calls to realize > @@ -81,23 +116,12 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], > { > /* Doing lazy PLT relocations; they need very little info. */ > for (; r < end; ++r) > -# ifdef ELF_MACHINE_IRELATIVE > - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) > - { > - if (r2 == NULL) > - r2 = r; > - end2 = r; > - } > - else > -# endif > - elf_machine_lazy_rel (map, scope, l_addr, r, skip_ifunc); > - > -# ifdef ELF_MACHINE_IRELATIVE > - if (r2 != NULL) > - for (; r2 <= end2; ++r2) > - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) > - elf_machine_lazy_rel (map, scope, l_addr, r2, skip_ifunc); > -# endif > + { > + const ElfW (Sym) *sym = &symtab[ELFW (R_SYM) (r->r_info)]; > + if (elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + elf_machine_lazy_rel (map, scope, l_addr, r, 0); > + } > } > else > #endif > @@ -125,46 +149,13 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], > const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; > void *const r_addr_arg = (void *) (l_addr + r->r_offset); > const struct r_found_version *rversion = &map->l_versions[ndx]; > -#if defined ELF_MACHINE_IRELATIVE > - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) > - { > - if (r2 == NULL) > - r2 = r; > - end2 = r; > - continue; > - } > -#endif > > - elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, > - skip_ifunc); > -#if defined SHARED > - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_JMP_SLOT > - && GLRO(dl_naudit) > 0) > - { > - struct link_map *sym_map > - = RESOLVE_MAP (map, scope, &sym, rversion, > - ELF_MACHINE_JMP_SLOT); > - if (sym != NULL) > - _dl_audit_symbind (map, NULL, r, sym, r_addr_arg, sym_map, > - false); > - } > -#endif > + if (elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0); > + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, rversion, > + r_addr_arg); > } > - > -#if defined ELF_MACHINE_IRELATIVE > - if (r2 != NULL) > - for (; r2 <= end2; ++r2) > - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) > - { > - ElfW(Half) ndx > - = version[ELFW(R_SYM) (r2->r_info)] & 0x7fff; > - elf_machine_rel (map, scope, r2, > - &symtab[ELFW(R_SYM) (r2->r_info)], > - &map->l_versions[ndx], > - (void *) (l_addr + r2->r_offset), > - skip_ifunc); > - } > -#endif > } > else > { > @@ -172,46 +163,97 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], > { > const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; > void *const r_addr_arg = (void *) (l_addr + r->r_offset); > -# ifdef ELF_MACHINE_IRELATIVE > - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) > - { > - if (r2 == NULL) > - r2 = r; > - end2 = r; > - continue; > - } > -# endif > - elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, > - skip_ifunc); > -# if defined SHARED > - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_JMP_SLOT > - && GLRO(dl_naudit) > 0) > - { > - struct link_map *sym_map > - = RESOLVE_MAP (map, scope, &sym, > - (struct r_found_version *) NULL, > - ELF_MACHINE_JMP_SLOT); > - if (sym != NULL) > - _dl_audit_symbind (map, NULL, r, sym,r_addr_arg, sym_map, > - false); > - } > -# endif > - } > > -# ifdef ELF_MACHINE_IRELATIVE > - if (r2 != NULL) > - for (; r2 <= end2; ++r2) > - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) > - elf_machine_rel (map, scope, r2, &symtab[ELFW(R_SYM) (r2->r_info)], > - NULL, (void *) (l_addr + r2->r_offset), > - skip_ifunc); > -# endif > + if (elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, 0); > + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, NULL, > + r_addr_arg); > + } > } > } > #endif /* !RTLD_BOOTSTRAP */ > } > > +/* Process only IRELATIVE entries (and other relocations targeting a defined > + STT_GNU_IFUNC symbol) in the relocation range [reladdr, reladdr+relsize). > + The first NRELATIVE entries are R_*_RELATIVE and are skipped without > + inspection. When lazy is non-zero the PLT lazy-binding path > + (elf_machine_lazy_rel) is used, otherwise the full non-lazy path > + (elf_machine_rel) is used. > + > + Called by _ELF_DYNAMIC_DO_RELOC after all non-IRELATIVE relocations have > + been processed for both .rela.dyn and .rela.plt, so that IRELATIVE > + resolvers may call PLT stubs safely regardless of which section the linker > + placed R_*_IRELATIVE in. */ > +static __always_inline void > +elf_dynamic_do_Rel_irelative (struct link_map *map, > + struct r_scope_elem *scope[], > + ElfW(Addr) reladdr, ElfW(Addr) relsize, > + __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative, > + int lazy, int skip_ifunc) > +{ > +# ifdef ELF_MACHINE_IRELATIVE > + const ElfW(Rel) *r = (const ElfW(Rel) *) reladdr + nrelative; > + const ElfW(Rel) *end = (const void *) (reladdr + relsize); > + ElfW(Addr) l_addr = map->l_addr; > + const ElfW(Sym) *const symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]); > + > + if (lazy) > + { > + for (; r < end; ++r) > + { > + const ElfW (Sym) *sym = &symtab[ELFW (R_SYM) (r->r_info)]; > + if (!elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + elf_machine_lazy_rel (map, scope, l_addr, r, skip_ifunc); > + } > + } > + else > + { > + if (map->l_info[VERSYMIDX (DT_VERSYM)]) > + { > + const ElfW(Half) *const version = > + (const void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]); > + > + for (; r < end; ++r) > + { > + const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; > + void *const r_addr_arg = (void *) (l_addr + r->r_offset); > + if (!elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + > + ElfW(Half) ndx = version[ELFW(R_SYM) (r->r_info)] & 0x7fff; > + const struct r_found_version *rversion = &map->l_versions[ndx]; > + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, > + skip_ifunc); > + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, rversion, > + r_addr_arg); > + } > + } > + else > + { > + for (; r < end; ++r) > + { > + const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; > + void *const r_addr_arg = (void *) (l_addr + r->r_offset); > + if (!elf_dynamic_is_Rel_irelative (r, sym)) > + continue; > + > + elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, > + skip_ifunc); > + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, NULL, > + r_addr_arg); > + } > + } > + } > +# endif > +} > + > #undef elf_dynamic_do_Rel > +#undef elf_dynamic_do_Rel_irelative > +#undef elf_dynamic_is_Rel_irelative > +#undef elf_dynamic_Rel_audit_symbind > #undef Rel > #undef elf_machine_rel > #undef elf_machine_rel_relative > diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h > index a46f36b8d45..334e241f63c 100644 > --- a/elf/dynamic-link.h > +++ b/elf/dynamic-link.h > @@ -78,7 +78,8 @@ 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. */ > > -# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, test_rel) \ > +# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, \ > + test_rel) \ > do { \ > struct { ElfW(Addr) start, size; \ > __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; } \ > @@ -118,13 +119,34 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[], > } \ > } \ > \ > - 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, \ > - skip_ifunc); \ > + /* Defer all IRELATIVE relocations until after all non-IRELATIVE \ > + relocations (including PLT lazy-binding setup) have been processed \ > + for both sections. This ensures IRELATIVE resolvers can call PLT \ > + stubs safely regardless of which section R_*_IRELATIVE was placed in \ > + 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); \ > + } \ > + else \ > + 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); \ > } while (0) > > # if ELF_MACHINE_NO_REL || ELF_MACHINE_NO_RELA > diff --git a/elf/tst-ifunc-plt-bindnow.c b/elf/tst-ifunc-plt-bindnow.c > new file mode 100644 > index 00000000000..3c5465ecd79 > --- /dev/null > +++ b/elf/tst-ifunc-plt-bindnow.c > @@ -0,0 +1,24 @@ > +/* Test that IRELATIVE resolvers may call PLT functions with LD_BIND_NOW=1. > + Copyright (C) 2019-2026 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > +/* Same as tst-ifunc-plt, but the test is run with LD_BIND_NOW=1 so the > + library is processed eagerly. Under eager binding the JMP_SLOT entries > + in .rela.plt are resolved by elf_machine_rel during relocation; this > + exercises the eager path of the deferred IRELATIVE processing. */ > + > +#include "tst-ifunc-plt.c" > diff --git a/elf/tst-ifunc-plt-dep.c b/elf/tst-ifunc-plt-dep.c > new file mode 100644 > index 00000000000..9cf2b1b0b24 > --- /dev/null > +++ b/elf/tst-ifunc-plt-dep.c > @@ -0,0 +1,23 @@ > +/* Dependency library for tst-ifunc-plt. > + 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/>. */ > + > +int > +get_value (void) > +{ > + return 42; > +} > diff --git a/elf/tst-ifunc-plt-dlopen-bindnow.c b/elf/tst-ifunc-plt-dlopen-bindnow.c > new file mode 100644 > index 00000000000..50c4f65a09d > --- /dev/null > +++ b/elf/tst-ifunc-plt-dlopen-bindnow.c > @@ -0,0 +1,45 @@ > +/* Test that IRELATIVE resolvers may call PLT functions via dlopen + RTLD_NOW. > + Copyright (C) 2026 Free Software Foundation, Inc. > + This file is part of the GNU C Library. > + > + The GNU C Library is free software; you can redistribute it and/or > + modify it under the terms of the GNU Lesser General Public > + License as published by the Free Software Foundation; either > + version 2.1 of the License, or (at your option) any later version. > + > + The GNU C Library is distributed in the hope that it will be useful, > + but WITHOUT ANY WARRANTY; without even the implied warranty of > + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + Lesser General Public License for more details. > + > + You should have received a copy of the GNU Lesser General Public > + License along with the GNU C Library; if not, see > + <https://www.gnu.org/licenses/>. */ > + > + > +/* Same as tst-ifunc-plt-dlopen, but uses RTLD_NOW to force eager > + relocation of the loaded library. This exercises the dlopen entry > + point along the eager-binding path. */ > + > +#include <support/xdlfcn.h> > +#include <support/check.h> > + > +typedef int (*fn_t) (void); > + > +static int > +do_test (void) > +{ > + void *handle = xdlopen ("tst-ifunc-plt-lib.so", RTLD_NOW | RTLD_LOCAL); > + > + fn_t compute_a = (fn_t) xdlsym (handle, "compute_a"); > + TEST_COMPARE (compute_a (), 1); > + > + fn_t compute_b = (fn_t) xdlsym (handle, "compute_b"); > + TEST_COMPARE (compute_b (), 2); > + > + xdlclose (handle); > + > + return 0; > +} > + > +#include <support/test-driver.c> > diff --git a/elf/tst-ifunc-plt-dlopen.c b/elf/tst-ifunc-plt-dlopen.c > new file mode 100644 > index 00000000000..ca31b8a29d9 > --- /dev/null > +++ b/elf/tst-ifunc-plt-dlopen.c > @@ -0,0 +1,46 @@ > +/* Test that IRELATIVE resolvers may call PLT functions on a dlopen'd library. > + 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/>. */ > + > + > +/* dlopen tst-ifunc-plt-lib.so with RTLD_LAZY and verify that the IFUNC > + resolver (which calls get_value() via PLT) ran successfully. This > + exercises the same _dl_relocate_object code path as startup loading > + but via the dlopen entry point. */ > + > +#include <support/xdlfcn.h> > +#include <support/check.h> > + > +typedef int (*fn_t) (void); > + > +static int > +do_test (void) > +{ > + void *handle = xdlopen ("tst-ifunc-plt-lib.so", RTLD_LAZY | RTLD_LOCAL); > + > + fn_t compute_a = (fn_t) xdlsym (handle, "compute_a"); > + TEST_COMPARE (compute_a (), 1); > + > + fn_t compute_b = (fn_t) xdlsym (handle, "compute_b"); > + TEST_COMPARE (compute_b (), 2); > + > + xdlclose (handle); > + > + return 0; > +} > + > +#include <support/test-driver.c> > diff --git a/elf/tst-ifunc-plt-lib.c b/elf/tst-ifunc-plt-lib.c > new file mode 100644 > index 00000000000..a9c100a8941 > --- /dev/null > +++ b/elf/tst-ifunc-plt-lib.c > @@ -0,0 +1,56 @@ > +/* Shared library for tst-ifunc-plt. > + Two static IFUNCs whose resolvers both call get_value() via PLT. > + 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/>. */ > + > + > +/* Both resolvers call get_value() via PLT (one JUMP_SLOT entry in > + .rel{a}.plt). This verifies that every IRELATIVE entry is deferred > + until after .rela.plt has been processed, not just the first one. */ > + > +#include <stddef.h> > + > +extern int get_value (void); > + > +static int > +impl_a (void) > +{ > + return 1; > +} > + > +static int > +impl_b (void) > +{ > + return 2; > +} > + > +static int (* > +resolve_a (void)) (void) > +{ > + return get_value () == 42 ? impl_a : NULL; > +} > + > +static int (* > +resolve_b (void)) (void) > +{ > + return get_value () == 42 ? impl_b : NULL; > +} > + > +/* The test is only built for $(have-ifunc), so we can assume HAVE_GCC_IFUNC > + here. */ > +int compute_a (void) __attribute__ ((ifunc ("resolve_a"))); > +int compute_b (void) __attribute__ ((ifunc ("resolve_b"))); > diff --git a/elf/tst-ifunc-plt.c b/elf/tst-ifunc-plt.c > new file mode 100644 > index 00000000000..78488673e74 > --- /dev/null > +++ b/elf/tst-ifunc-plt.c > @@ -0,0 +1,38 @@ > +/* Test that IRELATIVE resolvers may call PLT functions during startup. > + 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/>. */ > + > + > +/* tst-ifunc-plt-multi-lib.so defines two static IFUNCs (compute_a and > + compute_b), each producing an R_*_IRELATIVE in .rel{a}.dyn, with both > + resolvers calling get_value() via PLT. The test verifies that both > + IRELATIVEs are deferred until after .rel{a}.plt is processed. */ > + > +#include <support/check.h> > + > +extern int compute_a (void); > +extern int compute_b (void); > + > +static int > +do_test (void) > +{ > + TEST_COMPARE (compute_a (), 1); > + TEST_COMPARE (compute_b (), 2); > + return 0; > +} > + > +#include <support/test-driver.c> > diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h > index 21af8bc56e8..15651c62f3d 100644 > --- a/sysdeps/aarch64/dl-machine.h > +++ b/sysdeps/aarch64/dl-machine.h > @@ -119,6 +119,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[], > | (((type) == R_AARCH64_COPY) * ELF_RTYPE_CLASS_COPY)) > > #define ELF_MACHINE_JMP_SLOT R_AARCH64_JUMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_AARCH64_IRELATIVE > > #define DL_PLATFORM_INIT dl_platform_init () > > diff --git a/sysdeps/arm/dl-machine.h b/sysdeps/arm/dl-machine.h > index e0065ce73c8..15cced693e3 100644 > --- a/sysdeps/arm/dl-machine.h > +++ b/sysdeps/arm/dl-machine.h > @@ -190,6 +190,7 @@ _dl_start_user:\n\ > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_ARM_JUMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_ARM_IRELATIVE > > /* We define an initialization functions. This is called very early in > _dl_sysdep_start. */ > diff --git a/sysdeps/i386/dl-machine.h b/sysdeps/i386/dl-machine.h > index 6657f687917..dd49079d75c 100644 > --- a/sysdeps/i386/dl-machine.h > +++ b/sysdeps/i386/dl-machine.h > @@ -190,6 +190,7 @@ _dl_start_user:\n\ > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_386_JMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_386_IRELATIVE > > /* We define an initialization functions. This is called very early in > _dl_sysdep_start. */ > diff --git a/sysdeps/powerpc/powerpc32/dl-machine.h b/sysdeps/powerpc/powerpc32/dl-machine.h > index d7872986367..e07a44a5a5b 100644 > --- a/sysdeps/powerpc/powerpc32/dl-machine.h > +++ b/sysdeps/powerpc/powerpc32/dl-machine.h > @@ -146,6 +146,7 @@ __elf_preferred_address(struct link_map *loader, size_t maplength, > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_PPC_JMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_PPC_IRELATIVE > > /* We define an initialization function to initialize HWCAP/HWCAP2 and > platform data so it can be copied into the TCB later. This is called > diff --git a/sysdeps/powerpc/powerpc64/dl-machine.h b/sysdeps/powerpc/powerpc64/dl-machine.h > index 6e3771c91cc..1f5d7a0170b 100644 > --- a/sysdeps/powerpc/powerpc64/dl-machine.h > +++ b/sysdeps/powerpc/powerpc64/dl-machine.h > @@ -304,6 +304,7 @@ BODY_PREFIX "_dl_start_user:\n" \ > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_PPC64_JMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_PPC64_IRELATIVE > > /* We define an initialization function to initialize HWCAP/HWCAP2 and > platform data so it can be copied into the TCB later. This is called > diff --git a/sysdeps/riscv/dl-machine.h b/sysdeps/riscv/dl-machine.h > index 8c7312ad981..babb52af20a 100644 > --- a/sysdeps/riscv/dl-machine.h > +++ b/sysdeps/riscv/dl-machine.h > @@ -42,6 +42,7 @@ > #endif > > #define ELF_MACHINE_JMP_SLOT R_RISCV_JUMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_RISCV_IRELATIVE > > #define elf_machine_type_class(type) \ > ((ELF_RTYPE_CLASS_PLT * ((type) == ELF_MACHINE_JMP_SLOT \ > diff --git a/sysdeps/sparc/sparc32/dl-machine.h b/sysdeps/sparc/sparc32/dl-machine.h > index 73b347bfdf1..3771339a04a 100644 > --- a/sysdeps/sparc/sparc32/dl-machine.h > +++ b/sysdeps/sparc/sparc32/dl-machine.h > @@ -156,6 +156,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[], > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_SPARC_JMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_SPARC_IRELATIVE > > /* Undo the sub %sp, 6*4, %sp; add %sp, 22*4, %o0 below to get at the > value we want in __libc_stack_end. */ > diff --git a/sysdeps/sparc/sparc64/dl-machine.h b/sysdeps/sparc/sparc64/dl-machine.h > index 856922680a1..8a1a38d1701 100644 > --- a/sysdeps/sparc/sparc64/dl-machine.h > +++ b/sysdeps/sparc/sparc64/dl-machine.h > @@ -119,6 +119,7 @@ elf_machine_plt_value (struct link_map *map, const Elf64_Rela *reloc, > > /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ > #define ELF_MACHINE_JMP_SLOT R_SPARC_JMP_SLOT > +#define ELF_MACHINE_IRELATIVE R_SPARC_IRELATIVE > > /* Set up the loaded object described by L so its unrelocated PLT > entries will jump to the on-demand fixup code in dl-runtime.c. */ > -- > 2.43.0 > LGTM. Reviewed-by: H.J. Lu <hjl.tools@gmail.com> Thanks.
diff --git a/elf/Makefile b/elf/Makefile index f4d22c15991..0f888887be7 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -1255,6 +1255,10 @@ ifeq (yes,$(build-shared)) tests += \ tst-ifunc-fault-bindnow \ tst-ifunc-fault-lazy \ + tst-ifunc-plt \ + tst-ifunc-plt-bindnow \ + tst-ifunc-plt-dlopen \ + tst-ifunc-plt-dlopen-bindnow \ # tests # Note: sysdeps/x86_64/ifuncmain8.c uses ifuncmain8. tests-internal += \ @@ -1317,6 +1321,8 @@ modules-names += \ ifuncmod1 \ ifuncmod3 \ ifuncmod6 \ + tst-ifunc-plt-dep \ + tst-ifunc-plt-lib \ # modules-names ifeq (no,$(with-lld)) modules-names += ifuncmod5 @@ -1796,6 +1802,7 @@ unload4mod1.so-no-z-defs = yes ifuncmod1.so-no-z-defs = yes ifuncmod5.so-no-z-defs = yes ifuncmod6.so-no-z-defs = yes +tst-ifunc-plt-lib.so-no-z-defs = yes tst-auditmod9a.so-no-z-defs = yes tst-auditmod9b.so-no-z-defs = yes tst-nodelete-uniquemod.so-no-z-defs = yes @@ -2429,6 +2436,19 @@ $(objpfx)tst-ifunc-fault-bindnow.out: $(objpfx)tst-ifunc-fault-bindnow \ $(objpfx)ld.so $(tst-ifunc-fault-script) +LDFLAGS-tst-ifunc-plt-lib.so = -Wl,-z,lazy + +tst-ifunc-plt-bindnow-ENV = LD_BIND_NOW=1 +tst-ifunc-plt-dlopen-bindnow-ENV = LD_BIND_NOW=1 + +$(objpfx)tst-ifunc-plt-lib.so: $(objpfx)tst-ifunc-plt-dep.so +$(objpfx)tst-ifunc-plt: $(objpfx)tst-ifunc-plt-lib.so +$(objpfx)tst-ifunc-plt-bindnow: $(objpfx)tst-ifunc-plt-lib.so +$(objpfx)tst-ifunc-plt-dlopen.out: \ + $(objpfx)tst-ifunc-plt-lib.so $(objpfx)tst-ifunc-plt-dep.so +$(objpfx)tst-ifunc-plt-dlopen-bindnow.out: \ + $(objpfx)tst-ifunc-plt-lib.so $(objpfx)tst-ifunc-plt-dep.so + $(objpfx)tst-unique1.out: $(objpfx)tst-unique1mod1.so \ $(objpfx)tst-unique1mod2.so diff --git a/elf/do-rel.h b/elf/do-rel.h index d00ffab7e96..c7a8be368f0 100644 --- a/elf/do-rel.h +++ b/elf/do-rel.h @@ -23,6 +23,9 @@ #ifdef DO_RELA # define elf_dynamic_do_Rel elf_dynamic_do_Rela +# define elf_dynamic_do_Rel_irelative elf_dynamic_do_Rela_irelative +# define elf_dynamic_is_Rel_irelative elf_dynamic_is_Rela_irelative +# define elf_dynamic_Rel_audit_symbind elf_dynamic_Rela_audit_symbind # define Rel Rela # define elf_machine_rel elf_machine_rela # define elf_machine_rel_relative elf_machine_rela_relative @@ -34,16 +37,53 @@ (void *) (l_addr + relative->r_offset)) #endif +static __always_inline bool +elf_dynamic_is_Rel_irelative (const ElfW(Rel) *reloc, const ElfW(Sym) *sym) +{ +#ifdef ELF_MACHINE_IRELATIVE + const unsigned int r_type = ELFW (R_TYPE) (reloc->r_info); + return ((sym != NULL + && ELFW(ST_TYPE) (sym->st_info) == STT_GNU_IFUNC + && sym->st_shndx != SHN_UNDEF) + || r_type == ELF_MACHINE_IRELATIVE); +#else + return false; +#endif +} + +static __always_inline void +elf_dynamic_Rel_audit_symbind (struct link_map *map, + struct r_scope_elem *scope[], + const ElfW(Rel) *reloc, const ElfW(Sym) *sym, + const struct r_found_version *rversion, + void *r_addr_arg) +{ +#if defined SHARED + if (ELFW(R_TYPE) (reloc->r_info) == ELF_MACHINE_JMP_SLOT + && GLRO(dl_naudit) > 0) + { + struct link_map *sym_map + = RESOLVE_MAP (map, scope, &sym, rversion, ELF_MACHINE_JMP_SLOT); + if (sym != NULL) + _dl_audit_symbind (map, NULL, reloc, sym, r_addr_arg, sym_map, false); + } +#endif +} + /* Perform the relocations in MAP on the running program image as specified by RELTAG, SZTAG. If LAZY is nonzero, this is the first pass on PLT relocations; they should be set up to call _dl_runtime_resolve, rather - than fully resolved now. */ + than fully resolved now. + + IRELATIVE entries are always skipped (non-bootstrap); they are handled + separately by elf_dynamic_do_Rel_irelative after all other relocations + for both .rel.dyn and .rel.plt have been processed. */ static inline void __attribute__ ((always_inline)) elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], ElfW(Addr) reladdr, ElfW(Addr) relsize, __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative, - int lazy, int skip_ifunc) + int lazy) { const ElfW(Rel) *relative = (const void *) reladdr; const ElfW(Rel) *r = relative + nrelative; @@ -65,14 +105,9 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], void *const r_addr_arg = (void *) (l_addr + r->r_offset); const struct r_found_version *rversion = &map->l_versions[ndx]; - elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, skip_ifunc); + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0); } #else /* !RTLD_BOOTSTRAP */ -# if defined ELF_MACHINE_IRELATIVE - const ElfW(Rel) *r2 = NULL; - const ElfW(Rel) *end2 = NULL; -# endif - #if !defined DO_RELA || !defined ELF_MACHINE_PLT_REL /* We never bind lazily during ld.so bootstrap. Unfortunately gcc is not clever enough to see through all the function calls to realize @@ -81,23 +116,12 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], { /* Doing lazy PLT relocations; they need very little info. */ for (; r < end; ++r) -# ifdef ELF_MACHINE_IRELATIVE - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) - { - if (r2 == NULL) - r2 = r; - end2 = r; - } - else -# endif - elf_machine_lazy_rel (map, scope, l_addr, r, skip_ifunc); - -# ifdef ELF_MACHINE_IRELATIVE - if (r2 != NULL) - for (; r2 <= end2; ++r2) - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) - elf_machine_lazy_rel (map, scope, l_addr, r2, skip_ifunc); -# endif + { + const ElfW (Sym) *sym = &symtab[ELFW (R_SYM) (r->r_info)]; + if (elf_dynamic_is_Rel_irelative (r, sym)) + continue; + elf_machine_lazy_rel (map, scope, l_addr, r, 0); + } } else #endif @@ -125,46 +149,13 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; void *const r_addr_arg = (void *) (l_addr + r->r_offset); const struct r_found_version *rversion = &map->l_versions[ndx]; -#if defined ELF_MACHINE_IRELATIVE - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) - { - if (r2 == NULL) - r2 = r; - end2 = r; - continue; - } -#endif - elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, - skip_ifunc); -#if defined SHARED - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_JMP_SLOT - && GLRO(dl_naudit) > 0) - { - struct link_map *sym_map - = RESOLVE_MAP (map, scope, &sym, rversion, - ELF_MACHINE_JMP_SLOT); - if (sym != NULL) - _dl_audit_symbind (map, NULL, r, sym, r_addr_arg, sym_map, - false); - } -#endif + if (elf_dynamic_is_Rel_irelative (r, sym)) + continue; + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, 0); + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, rversion, + r_addr_arg); } - -#if defined ELF_MACHINE_IRELATIVE - if (r2 != NULL) - for (; r2 <= end2; ++r2) - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) - { - ElfW(Half) ndx - = version[ELFW(R_SYM) (r2->r_info)] & 0x7fff; - elf_machine_rel (map, scope, r2, - &symtab[ELFW(R_SYM) (r2->r_info)], - &map->l_versions[ndx], - (void *) (l_addr + r2->r_offset), - skip_ifunc); - } -#endif } else { @@ -172,46 +163,97 @@ elf_dynamic_do_Rel (struct link_map *map, struct r_scope_elem *scope[], { const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; void *const r_addr_arg = (void *) (l_addr + r->r_offset); -# ifdef ELF_MACHINE_IRELATIVE - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_IRELATIVE) - { - if (r2 == NULL) - r2 = r; - end2 = r; - continue; - } -# endif - elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, - skip_ifunc); -# if defined SHARED - if (ELFW(R_TYPE) (r->r_info) == ELF_MACHINE_JMP_SLOT - && GLRO(dl_naudit) > 0) - { - struct link_map *sym_map - = RESOLVE_MAP (map, scope, &sym, - (struct r_found_version *) NULL, - ELF_MACHINE_JMP_SLOT); - if (sym != NULL) - _dl_audit_symbind (map, NULL, r, sym,r_addr_arg, sym_map, - false); - } -# endif - } -# ifdef ELF_MACHINE_IRELATIVE - if (r2 != NULL) - for (; r2 <= end2; ++r2) - if (ELFW(R_TYPE) (r2->r_info) == ELF_MACHINE_IRELATIVE) - elf_machine_rel (map, scope, r2, &symtab[ELFW(R_SYM) (r2->r_info)], - NULL, (void *) (l_addr + r2->r_offset), - skip_ifunc); -# endif + if (elf_dynamic_is_Rel_irelative (r, sym)) + continue; + elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, 0); + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, NULL, + r_addr_arg); + } } } #endif /* !RTLD_BOOTSTRAP */ } +/* Process only IRELATIVE entries (and other relocations targeting a defined + STT_GNU_IFUNC symbol) in the relocation range [reladdr, reladdr+relsize). + The first NRELATIVE entries are R_*_RELATIVE and are skipped without + inspection. When lazy is non-zero the PLT lazy-binding path + (elf_machine_lazy_rel) is used, otherwise the full non-lazy path + (elf_machine_rel) is used. + + Called by _ELF_DYNAMIC_DO_RELOC after all non-IRELATIVE relocations have + been processed for both .rela.dyn and .rela.plt, so that IRELATIVE + resolvers may call PLT stubs safely regardless of which section the linker + placed R_*_IRELATIVE in. */ +static __always_inline void +elf_dynamic_do_Rel_irelative (struct link_map *map, + struct r_scope_elem *scope[], + ElfW(Addr) reladdr, ElfW(Addr) relsize, + __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative, + int lazy, int skip_ifunc) +{ +# ifdef ELF_MACHINE_IRELATIVE + const ElfW(Rel) *r = (const ElfW(Rel) *) reladdr + nrelative; + const ElfW(Rel) *end = (const void *) (reladdr + relsize); + ElfW(Addr) l_addr = map->l_addr; + const ElfW(Sym) *const symtab = (const void *) D_PTR (map, l_info[DT_SYMTAB]); + + if (lazy) + { + for (; r < end; ++r) + { + const ElfW (Sym) *sym = &symtab[ELFW (R_SYM) (r->r_info)]; + if (!elf_dynamic_is_Rel_irelative (r, sym)) + continue; + elf_machine_lazy_rel (map, scope, l_addr, r, skip_ifunc); + } + } + else + { + if (map->l_info[VERSYMIDX (DT_VERSYM)]) + { + const ElfW(Half) *const version = + (const void *) D_PTR (map, l_info[VERSYMIDX (DT_VERSYM)]); + + for (; r < end; ++r) + { + const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; + void *const r_addr_arg = (void *) (l_addr + r->r_offset); + if (!elf_dynamic_is_Rel_irelative (r, sym)) + continue; + + ElfW(Half) ndx = version[ELFW(R_SYM) (r->r_info)] & 0x7fff; + const struct r_found_version *rversion = &map->l_versions[ndx]; + elf_machine_rel (map, scope, r, sym, rversion, r_addr_arg, + skip_ifunc); + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, rversion, + r_addr_arg); + } + } + else + { + for (; r < end; ++r) + { + const ElfW(Sym) *sym = &symtab[ELFW(R_SYM) (r->r_info)]; + void *const r_addr_arg = (void *) (l_addr + r->r_offset); + if (!elf_dynamic_is_Rel_irelative (r, sym)) + continue; + + elf_machine_rel (map, scope, r, sym, NULL, r_addr_arg, + skip_ifunc); + elf_dynamic_Rel_audit_symbind (map, scope, r, sym, NULL, + r_addr_arg); + } + } + } +# endif +} + #undef elf_dynamic_do_Rel +#undef elf_dynamic_do_Rel_irelative +#undef elf_dynamic_is_Rel_irelative +#undef elf_dynamic_Rel_audit_symbind #undef Rel #undef elf_machine_rel #undef elf_machine_rel_relative diff --git a/elf/dynamic-link.h b/elf/dynamic-link.h index a46f36b8d45..334e241f63c 100644 --- a/elf/dynamic-link.h +++ b/elf/dynamic-link.h @@ -78,7 +78,8 @@ 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. */ -# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, test_rel) \ +# define _ELF_DYNAMIC_DO_RELOC(RELOC, reloc, map, scope, do_lazy, skip_ifunc, \ + test_rel) \ do { \ struct { ElfW(Addr) start, size; \ __typeof (((ElfW(Dyn) *) 0)->d_un.d_val) nrelative; int lazy; } \ @@ -118,13 +119,34 @@ elf_machine_lazy_rel (struct link_map *map, struct r_scope_elem *scope[], } \ } \ \ - 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, \ - skip_ifunc); \ + /* Defer all IRELATIVE relocations until after all non-IRELATIVE \ + relocations (including PLT lazy-binding setup) have been processed \ + for both sections. This ensures IRELATIVE resolvers can call PLT \ + stubs safely regardless of which section R_*_IRELATIVE was placed in \ + 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); \ + } \ + else \ + 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); \ } while (0) # if ELF_MACHINE_NO_REL || ELF_MACHINE_NO_RELA diff --git a/elf/tst-ifunc-plt-bindnow.c b/elf/tst-ifunc-plt-bindnow.c new file mode 100644 index 00000000000..3c5465ecd79 --- /dev/null +++ b/elf/tst-ifunc-plt-bindnow.c @@ -0,0 +1,24 @@ +/* Test that IRELATIVE resolvers may call PLT functions with LD_BIND_NOW=1. + Copyright (C) 2019-2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + +/* Same as tst-ifunc-plt, but the test is run with LD_BIND_NOW=1 so the + library is processed eagerly. Under eager binding the JMP_SLOT entries + in .rela.plt are resolved by elf_machine_rel during relocation; this + exercises the eager path of the deferred IRELATIVE processing. */ + +#include "tst-ifunc-plt.c" diff --git a/elf/tst-ifunc-plt-dep.c b/elf/tst-ifunc-plt-dep.c new file mode 100644 index 00000000000..9cf2b1b0b24 --- /dev/null +++ b/elf/tst-ifunc-plt-dep.c @@ -0,0 +1,23 @@ +/* Dependency library for tst-ifunc-plt. + 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/>. */ + +int +get_value (void) +{ + return 42; +} diff --git a/elf/tst-ifunc-plt-dlopen-bindnow.c b/elf/tst-ifunc-plt-dlopen-bindnow.c new file mode 100644 index 00000000000..50c4f65a09d --- /dev/null +++ b/elf/tst-ifunc-plt-dlopen-bindnow.c @@ -0,0 +1,45 @@ +/* Test that IRELATIVE resolvers may call PLT functions via dlopen + RTLD_NOW. + Copyright (C) 2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + <https://www.gnu.org/licenses/>. */ + + +/* Same as tst-ifunc-plt-dlopen, but uses RTLD_NOW to force eager + relocation of the loaded library. This exercises the dlopen entry + point along the eager-binding path. */ + +#include <support/xdlfcn.h> +#include <support/check.h> + +typedef int (*fn_t) (void); + +static int +do_test (void) +{ + void *handle = xdlopen ("tst-ifunc-plt-lib.so", RTLD_NOW | RTLD_LOCAL); + + fn_t compute_a = (fn_t) xdlsym (handle, "compute_a"); + TEST_COMPARE (compute_a (), 1); + + fn_t compute_b = (fn_t) xdlsym (handle, "compute_b"); + TEST_COMPARE (compute_b (), 2); + + xdlclose (handle); + + return 0; +} + +#include <support/test-driver.c> diff --git a/elf/tst-ifunc-plt-dlopen.c b/elf/tst-ifunc-plt-dlopen.c new file mode 100644 index 00000000000..ca31b8a29d9 --- /dev/null +++ b/elf/tst-ifunc-plt-dlopen.c @@ -0,0 +1,46 @@ +/* Test that IRELATIVE resolvers may call PLT functions on a dlopen'd library. + 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/>. */ + + +/* dlopen tst-ifunc-plt-lib.so with RTLD_LAZY and verify that the IFUNC + resolver (which calls get_value() via PLT) ran successfully. This + exercises the same _dl_relocate_object code path as startup loading + but via the dlopen entry point. */ + +#include <support/xdlfcn.h> +#include <support/check.h> + +typedef int (*fn_t) (void); + +static int +do_test (void) +{ + void *handle = xdlopen ("tst-ifunc-plt-lib.so", RTLD_LAZY | RTLD_LOCAL); + + fn_t compute_a = (fn_t) xdlsym (handle, "compute_a"); + TEST_COMPARE (compute_a (), 1); + + fn_t compute_b = (fn_t) xdlsym (handle, "compute_b"); + TEST_COMPARE (compute_b (), 2); + + xdlclose (handle); + + return 0; +} + +#include <support/test-driver.c> diff --git a/elf/tst-ifunc-plt-lib.c b/elf/tst-ifunc-plt-lib.c new file mode 100644 index 00000000000..a9c100a8941 --- /dev/null +++ b/elf/tst-ifunc-plt-lib.c @@ -0,0 +1,56 @@ +/* Shared library for tst-ifunc-plt. + Two static IFUNCs whose resolvers both call get_value() via PLT. + 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/>. */ + + +/* Both resolvers call get_value() via PLT (one JUMP_SLOT entry in + .rel{a}.plt). This verifies that every IRELATIVE entry is deferred + until after .rela.plt has been processed, not just the first one. */ + +#include <stddef.h> + +extern int get_value (void); + +static int +impl_a (void) +{ + return 1; +} + +static int +impl_b (void) +{ + return 2; +} + +static int (* +resolve_a (void)) (void) +{ + return get_value () == 42 ? impl_a : NULL; +} + +static int (* +resolve_b (void)) (void) +{ + return get_value () == 42 ? impl_b : NULL; +} + +/* The test is only built for $(have-ifunc), so we can assume HAVE_GCC_IFUNC + here. */ +int compute_a (void) __attribute__ ((ifunc ("resolve_a"))); +int compute_b (void) __attribute__ ((ifunc ("resolve_b"))); diff --git a/elf/tst-ifunc-plt.c b/elf/tst-ifunc-plt.c new file mode 100644 index 00000000000..78488673e74 --- /dev/null +++ b/elf/tst-ifunc-plt.c @@ -0,0 +1,38 @@ +/* Test that IRELATIVE resolvers may call PLT functions during startup. + 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/>. */ + + +/* tst-ifunc-plt-multi-lib.so defines two static IFUNCs (compute_a and + compute_b), each producing an R_*_IRELATIVE in .rel{a}.dyn, with both + resolvers calling get_value() via PLT. The test verifies that both + IRELATIVEs are deferred until after .rel{a}.plt is processed. */ + +#include <support/check.h> + +extern int compute_a (void); +extern int compute_b (void); + +static int +do_test (void) +{ + TEST_COMPARE (compute_a (), 1); + TEST_COMPARE (compute_b (), 2); + return 0; +} + +#include <support/test-driver.c> diff --git a/sysdeps/aarch64/dl-machine.h b/sysdeps/aarch64/dl-machine.h index 21af8bc56e8..15651c62f3d 100644 --- a/sysdeps/aarch64/dl-machine.h +++ b/sysdeps/aarch64/dl-machine.h @@ -119,6 +119,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[], | (((type) == R_AARCH64_COPY) * ELF_RTYPE_CLASS_COPY)) #define ELF_MACHINE_JMP_SLOT R_AARCH64_JUMP_SLOT +#define ELF_MACHINE_IRELATIVE R_AARCH64_IRELATIVE #define DL_PLATFORM_INIT dl_platform_init () diff --git a/sysdeps/arm/dl-machine.h b/sysdeps/arm/dl-machine.h index e0065ce73c8..15cced693e3 100644 --- a/sysdeps/arm/dl-machine.h +++ b/sysdeps/arm/dl-machine.h @@ -190,6 +190,7 @@ _dl_start_user:\n\ /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_ARM_JUMP_SLOT +#define ELF_MACHINE_IRELATIVE R_ARM_IRELATIVE /* We define an initialization functions. This is called very early in _dl_sysdep_start. */ diff --git a/sysdeps/i386/dl-machine.h b/sysdeps/i386/dl-machine.h index 6657f687917..dd49079d75c 100644 --- a/sysdeps/i386/dl-machine.h +++ b/sysdeps/i386/dl-machine.h @@ -190,6 +190,7 @@ _dl_start_user:\n\ /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_386_JMP_SLOT +#define ELF_MACHINE_IRELATIVE R_386_IRELATIVE /* We define an initialization functions. This is called very early in _dl_sysdep_start. */ diff --git a/sysdeps/powerpc/powerpc32/dl-machine.h b/sysdeps/powerpc/powerpc32/dl-machine.h index d7872986367..e07a44a5a5b 100644 --- a/sysdeps/powerpc/powerpc32/dl-machine.h +++ b/sysdeps/powerpc/powerpc32/dl-machine.h @@ -146,6 +146,7 @@ __elf_preferred_address(struct link_map *loader, size_t maplength, /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_PPC_JMP_SLOT +#define ELF_MACHINE_IRELATIVE R_PPC_IRELATIVE /* We define an initialization function to initialize HWCAP/HWCAP2 and platform data so it can be copied into the TCB later. This is called diff --git a/sysdeps/powerpc/powerpc64/dl-machine.h b/sysdeps/powerpc/powerpc64/dl-machine.h index 6e3771c91cc..1f5d7a0170b 100644 --- a/sysdeps/powerpc/powerpc64/dl-machine.h +++ b/sysdeps/powerpc/powerpc64/dl-machine.h @@ -304,6 +304,7 @@ BODY_PREFIX "_dl_start_user:\n" \ /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_PPC64_JMP_SLOT +#define ELF_MACHINE_IRELATIVE R_PPC64_IRELATIVE /* We define an initialization function to initialize HWCAP/HWCAP2 and platform data so it can be copied into the TCB later. This is called diff --git a/sysdeps/riscv/dl-machine.h b/sysdeps/riscv/dl-machine.h index 8c7312ad981..babb52af20a 100644 --- a/sysdeps/riscv/dl-machine.h +++ b/sysdeps/riscv/dl-machine.h @@ -42,6 +42,7 @@ #endif #define ELF_MACHINE_JMP_SLOT R_RISCV_JUMP_SLOT +#define ELF_MACHINE_IRELATIVE R_RISCV_IRELATIVE #define elf_machine_type_class(type) \ ((ELF_RTYPE_CLASS_PLT * ((type) == ELF_MACHINE_JMP_SLOT \ diff --git a/sysdeps/sparc/sparc32/dl-machine.h b/sysdeps/sparc/sparc32/dl-machine.h index 73b347bfdf1..3771339a04a 100644 --- a/sysdeps/sparc/sparc32/dl-machine.h +++ b/sysdeps/sparc/sparc32/dl-machine.h @@ -156,6 +156,7 @@ elf_machine_runtime_setup (struct link_map *l, struct r_scope_elem *scope[], /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_SPARC_JMP_SLOT +#define ELF_MACHINE_IRELATIVE R_SPARC_IRELATIVE /* Undo the sub %sp, 6*4, %sp; add %sp, 22*4, %o0 below to get at the value we want in __libc_stack_end. */ diff --git a/sysdeps/sparc/sparc64/dl-machine.h b/sysdeps/sparc/sparc64/dl-machine.h index 856922680a1..8a1a38d1701 100644 --- a/sysdeps/sparc/sparc64/dl-machine.h +++ b/sysdeps/sparc/sparc64/dl-machine.h @@ -119,6 +119,7 @@ elf_machine_plt_value (struct link_map *map, const Elf64_Rela *reloc, /* A reloc type used for ld.so cmdline arg lookups to reject PLT entries. */ #define ELF_MACHINE_JMP_SLOT R_SPARC_JMP_SLOT +#define ELF_MACHINE_IRELATIVE R_SPARC_IRELATIVE /* Set up the loaded object described by L so its unrelocated PLT entries will jump to the on-demand fixup code in dl-runtime.c. */
When a shared library is built with -z lazy and its IFUNC resolver calls a PLT function, the dynamic linker can crash. The resolver runs while the PLT stubs still hold their raw ELF virtual addresses — l_addr has not yet been added — so the call branches to an unmapped address. The old code deferred IRELATIVE entries only to the end of the relocation range currently being processed (via the r2/end2 scan-ahead mechanism in elf_dynamic_do_Rel). This was sufficient only when both IRELATIVE and the JMP_SLOT entries for the PLT functions it needs are in the same section. On x86-64, aarch64, arm, i386 and most other targets, a file-scope initialiser of the form int (*fptr)(void) = some_ifunc; causes the linker to place R_*_IRELATIVE in .rela.dyn, while JMP_SLOT entries for any PLT calls made by the resolver live in .rela.plt. Processing .rela.dyn before .rela.plt means the resolver fires before the PLT is usable, regardless of where within .rela.dyn IRELATIVE appears. Fix this by splitting IRELATIVE processing into a separate, explicitly deferred pass. In elf/do-rel.h: - Remove the r2/end2 variables and the post-loop IRELATIVE re-scan from elf_dynamic_do_Rel. IRELATIVE entries are now always skipped in the non-bootstrap path. - Add a new elf_dynamic_do_Rel_irelative function that scans a relocation range and calls elf_machine_rel/elf_machine_lazy_rel for IRELATIVE and ifunc relocations. In elf/dynamic-link.h, update _ELF_DYNAMIC_DO_RELOC to use a two-phase approach for non-bootstrap builds unconditionally (regardless of whether ranges[1].size is zero): Phase 1+2: elf_dynamic_do_Rel over .rela.dyn then .rela.plt — processes everything except IRELATIVE/STT_GNU_IFUNC. Phase 3+4: elf_dynamic_do_Rel_irelative over .rela.dyn then .rela.plt — processes only IRELATIVE, by which point all PLT stubs are valid. This guarantees that IRELATIVE resolvers can call PLT stubs safely regardless of which section the linker placed R_*_IRELATIVE in. Add ELF_MACHINE_IRELATIVE to the architectures that were missing it so the new skip logic in elf_dynamic_do_Rel is compiled for all targets. This patch addresses the binutils BZ 13302 [1] from the glibc side, and also fixes the mold-reported issue [2], which shows that IFUNC relocation placement and processing can work differently across ABIs. I checked on all ABIs that support IFUNC (x86_64, i686, aarch64, arm, loongarch, powerpc, riscv, s390, and sparc), some via qemu-system. [1] https://sourceware.org/bugzilla/show_bug.cgi?id=13302 [2] https://github.com/rui314/mold/issues/1550 -- Changes from v1: - Issue the la_symbind for JMP_SLOT entries that target a defined STT_GNU_IFUNC symbol. - elf/do-rel.h: pass nrelative through to elf_dynamic_do_Rel_irelative and skip the leading R_*_RELATIVE block without filter inspection (those entries never match the IRELATIVE/STT_GNU_IFUNC predicate). - elf/do-rel.h: add the missing #undef for elf_dynamic_is_Rel_irelative and elf_dynamic_Rel_audit_symbind. - Add elf/tst-ifunc-plt-bindnow.c and elf/tst-ifunc-plt-dlopen-bindnow.c test: LD_BIND_NOW=1 for the startup-load variant, RTLD_NOW for the dlopen variant. These exercise the elf_machine_rel path through elf_dynamic_do_Rel_irelative that the lazy tests do not. --- elf/Makefile | 20 +++ elf/do-rel.h | 234 +++++++++++++++---------- elf/dynamic-link.h | 38 +++- elf/tst-ifunc-plt-bindnow.c | 24 +++ elf/tst-ifunc-plt-dep.c | 23 +++ elf/tst-ifunc-plt-dlopen-bindnow.c | 45 +++++ elf/tst-ifunc-plt-dlopen.c | 46 +++++ elf/tst-ifunc-plt-lib.c | 56 ++++++ elf/tst-ifunc-plt.c | 38 ++++ sysdeps/aarch64/dl-machine.h | 1 + sysdeps/arm/dl-machine.h | 1 + sysdeps/i386/dl-machine.h | 1 + sysdeps/powerpc/powerpc32/dl-machine.h | 1 + sysdeps/powerpc/powerpc64/dl-machine.h | 1 + sysdeps/riscv/dl-machine.h | 1 + sysdeps/sparc/sparc32/dl-machine.h | 1 + sysdeps/sparc/sparc64/dl-machine.h | 1 + 17 files changed, 428 insertions(+), 104 deletions(-) create mode 100644 elf/tst-ifunc-plt-bindnow.c create mode 100644 elf/tst-ifunc-plt-dep.c create mode 100644 elf/tst-ifunc-plt-dlopen-bindnow.c create mode 100644 elf/tst-ifunc-plt-dlopen.c create mode 100644 elf/tst-ifunc-plt-lib.c create mode 100644 elf/tst-ifunc-plt.c