-
-
Notifications
You must be signed in to change notification settings - Fork 531
Description
Summary
Programs linked with mold (new PLT scheme, tested 2.35.1 and 2.40.4) crash with SIGSEGV at load time when a shared library contains:
- An
R_X86_64_IRELATIVErelocation in.rela.dyn - The IFUNC resolver calls an external function via PLT in the same library
The crash does not occur with LD_BIND_NOW=1, -Wl,-z,now, or older mold versions that use the traditional PLT scheme (tested 2.30.0).
Root Cause
Mold's new PLT scheme uses this structure:
PLT[0]: endbr64; push %r11; push GOT[1]; jmp *GOT[2]
PLT[n]: mov $index, %r11d; jmp *GOT[n]
The initial GOT.PLT entries contain PLT[0]'s ELF virtual address (e.g., 0x16a0). When a lazy PLT stub is called, jmp *GOT[n] should jump to PLT[0] (the resolver). But the stored address is an unrelocated offset, not the runtime address.
The dynamic linker normally fixes this by adding the load base address (l_addr) during .rela.plt processing (elf_machine_lazy_rel does *reloc_addr += l_addr).
However, R_X86_64_IRELATIVE relocations are in .rela.dyn, which is processed before .rela.plt. When the IRELATIVE resolver runs, the .rela.plt lazy setup has not yet occurred, so GOT.PLT entries still contain unrelocated addresses. If the resolver calls any function via PLT, the PLT stub jumps to the unrelocated address (unmapped memory) and crashes.
Why traditional PLT works
The traditional PLT scheme (GNU ld, gold, older mold) uses:
PLT[n]: jmp *GOT[n]; push $index; jmp PLT[0]
The initial GOT[n] points to the push $index instruction (the next instruction in the same PLT stub). The jmp *GOT[n] with this value falls through to push; jmp PLT[0], which works without any relocation because jmp PLT[0] uses relative encoding (e9 xx xx xx xx).
Why LD_BIND_NOW works
With LD_BIND_NOW=1, all JUMP_SLOT relocations are resolved eagerly. The dynamic linker fills GOT.PLT entries with actual function addresses before any IRELATIVE resolvers run.
Reproducer
helper.h
#ifdef __cplusplus
extern "C" {
#endif
int get_cpu_info(void);
#ifdef __cplusplus
}
#endifhelper.c
#include "helper.h"
int get_cpu_info(void) { return 42; }repro.cpp
#include "helper.h"
static int compute_default(int x) { return x + 1; }
static int compute_fast(int x) { return x + 2; }
typedef int (*compute_fn)(int);
extern "C" compute_fn resolve_compute() {
if (get_cpu_info() > 0) // PLT call -> crash
return compute_fast;
return compute_default;
}
extern "C" __attribute__((visibility("hidden")))
int compute(int) __attribute__((ifunc("resolve_compute")));
extern "C" int wrapper(int x) { return compute(x); }main.c
#include <stdio.h>
extern int wrapper(int x);
int main(void) {
printf("wrapper(10) = %d\n", wrapper(10));
return 0;
}Build & test
# Build with mold
gcc -shared -fPIC -o libhelper.so helper.c -B/usr/local/libexec/mold
g++ -shared -fPIC -o libfoo.so repro.cpp -L. -lhelper -B/usr/local/libexec/mold
gcc -o main main.c -L. -lfoo -lhelper -Wl,-rpath,'$ORIGIN' -B/usr/local/libexec/mold
LD_LIBRARY_PATH=. ./main # SIGSEGV
LD_LIBRARY_PATH=. LD_BIND_NOW=1 ./main # OK
# Build with GNU ld (works fine)
gcc -shared -fPIC -o libhelper.so helper.c
g++ -shared -fPIC -o libfoo.so repro.cpp -L. -lhelper
gcc -o main main.c -L. -lfoo -lhelper -Wl,-rpath,'$ORIGIN'
LD_LIBRARY_PATH=. ./main # OKReal-world Impact
LLVM/Clang (trunk, commit 146a919360eb) added __attribute__((target("default"))) and __attribute__((target("sse4.2"))) to fastParseASCIIIdentifier in clang/lib/Lex/Lexer.cpp. This creates an IFUNC with an auto-generated resolver that calls __cpu_indicator_init via PLT. When Clang is built with BUILD_SHARED_LIBS=ON and mold, the clang binary segfaults on startup.
Environment
Tested mold versions:
- mold 2.35.1 - new PLT scheme, crashes
- mold 2.40.4 - new PLT scheme, crashes
- mold 2.30.0 -
traditional PLT, worksnew PLT scheme, crashes
Other:
- GCC 11.2.1 (CentOS 7), GCC 13.3.0 (Ubuntu 24.04)
- glibc 2.17 (CentOS 7) and glibc 2.39 (Ubuntu 24.04) - both crash
- x86_64 Linux
Suggested Fix
One of:
- Use
R_X86_64_RELATIVErelocations for GOT.PLT initial values, so they are fixed up during.rela.dynprocessing (before IRELATIVE) - Revert to self-relative PLT fallback values that don't need relocation
- Process
.rela.pltlazy setup before IRELATIVE relocations in.rela.dyn(this would require a dynamic linker change, not a mold change)