Skip to content

Case 134: RELRO Weakened

Field Value
Verdict ๐ŸŸก COMPATIBLE_WITH_RISK
Category Risk
Platforms Linux
Flags Bad practice
Detected ChangeKinds relro_weakened
Source files examples/case134_relro_weakened/

Category: ELF / Security | Verdict: ๐ŸŸก COMPATIBLE_WITH_RISK

Verdict and consumer impact

Both libraries export identical symbols with identical signatures โ€” every existing consumer keeps working exactly as before, no recompilation needed. The difference is link hardening: v1 is linked with full RELRO (-Wl,-z,relro -Wl,-z,now), giving it a PT_GNU_RELRO program header and an eagerly-bound, read-only GOT after startup. v2 drops it (-Wl,-z,norelro), so the GNU_RELRO segment disappears and the GOT stays writable for the process lifetime โ€” a security regression (widens the attack surface for a GOT-overwrite exploit) even though the functional ABI is untouched.

Old/new diff

v1.c v2.c
int compute(int x) { return x * x + 1; }
int transform(int x, int y) { return x + y * 2; }
(linked -Wl,-z,relro -Wl,-z,now)
(identical source)
(linked -Wl,-z,norelro)

abicheck command

gcc -shared -fPIC -g v1.c -o libfoo_v1.so -Wl,-z,relro -Wl,-z,now
gcc -shared -fPIC -g v2.c -o libfoo_v2.so -Wl,-z,norelro
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: COMPATIBLE_WITH_RISK (exit 0)

- relro_weakened: RELRO weakened: full โ†’ none

Minimum evidence

min_evidence: L0 โ€” the PT_GNU_RELRO program header (or its absence) is visible directly in the ELF program-header table; no debug info or headers needed.

Why abicheck catches it

abicheck reads each binary's ELF program headers and classifies the RELRO level (none/partial/full) from the presence and flags of PT_GNU_RELRO plus the DT_BIND_NOW/DT_FLAGS dynamic tags; a downgrade between the two sides is reported directly from that L0 evidence.

Runtime failure demonstration

No observable effect on existing binaries. compute()/transform() produce identical output before and after the swap โ€” this is a deployment hardening regression, not a functional break:

gcc -shared -fPIC -g v1.c -o libfoo.so -Wl,-z,relro -Wl,-z,now
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ compute(7) = 50
# โ†’ transform(3, 4) = 11

# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so -Wl,-z,norelro
./app
# โ†’ compute(7) = 50          (unchanged)
# โ†’ transform(3, 4) = 11     (unchanged)

The regression only matters if an attacker can already write to the process's memory โ€” full RELRO removes the GOT as a target; norelro leaves it writable and exploitable for the process's whole lifetime.

Safe redesign

Keep full RELRO in release builds: -Wl,-z,relro -Wl,-z,now. Distribution hardening policies (Debian, Fedora) flag partial/absent RELRO on shared objects and can reject the package outright.

Cross-tool comparison

abidiff compares ABI XML dumps (symbols and types) and has no concept of link-time hardening flags, so it reports no difference at all between these two libraries โ€” RELRO weakening is invisible to it. checksec-style tools catch the hardening regression but don't do ABI diffing, so they'd miss a genuine symbol/type break in the same release. abicheck reports both from one pass over the binary.


Source files

  • CMakeLists.txt
  • app.c
  • v1.c
  • v2.c

See also: Examples overview ยท All COMPATIBLE_WITH_RISK cases ยท Category: Risk.