elf: Defer all IRELATIVE relocations until after PLT setup
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.