Case 52: RPATH Leak (Hardcoded Build Directory)¶
| Field | Value |
|---|---|
| Verdict | ๐ข COMPATIBLE |
| Category | Quality (Compatible) |
| Platforms | Linux |
| Flags | Bad practice |
Detected ChangeKinds |
runpath_changed |
| Source files | examples/case52_rpath_leak/ |
Category: Quality | Verdict: ๐ข COMPATIBLE (bad practice)
Verdict and consumer impact¶
Both libraries export identical symbols with identical signatures โ no
recompilation is ever required. The issue is entirely in deployment metadata:
v1 bakes an absolute build-machine path (/home/build/myproject/lib) into
DT_RUNPATH, while v2 uses an $ORIGIN-relative path. A binary linked
against v1 carries that build-directory path forward; on a machine without
that exact directory the loader silently falls through to other search
paths, and on a machine that does have a writable path matching it, an
attacker can plant a malicious library there for the loader to pick up.
Old/new diff¶
| old/lib.c | new/lib.c |
|---|---|
linked with -Wl,-rpath,/home/build/myproject/lib |
linked with -Wl,-rpath,$ORIGIN |
(library source is identical โ see old/lib.c / new/lib.c) |
abicheck command¶
gcc -shared -fPIC -g old/lib.c -o libfoo_v1.so -Wl,-rpath,/home/build/myproject/lib
gcc -shared -fPIC -g new/lib.c -o libfoo_v2.so '-Wl,-rpath,$ORIGIN'
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: COMPATIBLE (exit 0)
- runpath_changed: RUNPATH changed: '/home/build/myproject/lib' -> '$ORIGIN'
Minimum evidence¶
min_evidence: L0 โ DT_RUNPATH is a dynamic-section tag read directly
from the ELF .dynamic section; no debug info or headers are needed to see
the change.
Why abicheck catches it¶
abicheck reads each library's DT_RUNPATH/DT_RPATH entry from the ELF
dynamic section and diffs the string value between versions โ a pure L0 ELF
fact, unrelated to the exported symbol surface.
Runtime failure demonstration¶
No observable effect on existing binaries. The exported symbols and their behavior are byte-identical between versions:
readelf -d libfoo_v1.so | grep -E 'RPATH|RUNPATH'
# โ Library runpath: [/home/build/myproject/lib]
readelf -d libfoo_v2.so | grep -E 'RPATH|RUNPATH'
# โ Library runpath: [$ORIGIN]
encode()/decode() compute the same results with either .so loaded; the
risk is confined to which library the dynamic linker resolves at load
time, not to what the loaded library does once resolved.
Safe redesign¶
Use $ORIGIN-relative paths instead of absolute build paths:
In CMake, set a proper install RPATH:
set(CMAKE_INSTALL_RPATH "$ORIGIN")
set(CMAKE_BUILD_WITH_INSTALL_RPATH OFF)
set(CMAKE_INSTALL_RPATH_USE_LINK_LIBRARIES OFF)
Or strip RPATH entirely and rely on system search paths:
Real-world example: Fedora's packaging guidelines explicitly forbid
hardcoded RPATH โ the check-rpaths tool rejects any package with
non-standard paths in DT_RPATH/DT_RUNPATH. Debian's lintian reports
binary-or-shlib-defines-rpath as a warning for the same reason.
References¶
Source files¶
CMakeLists.txtapp.c
See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).