2026-08-25
Your binary's Global Offset Table (GOT) is a table of pointers the dynamic linker patches at load time. It's writable — it has to be, because ld.so writes the resolved addresses into it. But once relocation finishes, most of it never needs to change again. Leaving it writable is a gift to attackers: overwrite one GOT entry and the next call to free() jumps to your shellcode.
RELRO (RELocation Read-Only) fixes this by re-mapping those pages as read-only after the linker is done writing them. The mechanism is a dedicated ELF program header: PT_GNU_RELRO. It's not a segment that gets loaded — it points into an existing PT_LOAD segment and says "after relocation, mprotect this range to PROT_READ."
Two flavors exist:
-Wl,-z,relro): reorders sections so .got, .init_array, .fini_array, and .dynamic land on their own pages, then makes them read-only. But .got.plt — used by lazy PLT binding — stays writable, because the resolver needs to write there on every first call.-Wl,-z,relro,-z,now): forces the linker to resolve every PLT symbol at load time (BIND_NOW), so .got.plt can also become read-only. Slower startup, no lazy binding, but the entire GOT is locked.Concrete example. Compile a program with the classic __free_hook exploit pattern in mind. Without RELRO, you can spot the writable GOT with readelf -l ./a.out | grep GNU_RELRO (missing) and confirm with checksec --file=./a.out. With gcc -Wl,-z,now foo.c, run again — the RELRO line reads "Full RELRO." Try to mprotect-around it and you'll fault. Every distro package built after ~2016 ships with Full RELRO for setuid binaries and Partial for the rest.
Rule of thumb: Full RELRO adds one mprotect call and forces N symbol resolutions at startup, where N is your PLT size. For a program with 200 external calls, that's ~200µs of extra startup — invisible for a server, measurable for a CLI tool run in a tight loop. Partial RELRO is essentially free.
The subtle gotcha: RELRO only protects what the linker knows about. Runtime-allocated function-pointer tables (vtables in your own heap, JIT trampolines) get no protection — you need mprotect yourself, or a separate PROT_READ mapping.
PT_GNU_RELRO is the ELF header that tells the loader "flip these pages read-only after you're done writing to them," turning the GOT from an attacker's favorite hijack target into an immutable table — and Full RELRO extends that protection to .got.plt at the cost of eager symbol resolution.
