Skip to content

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:

gcc -shared -fPIC lib.c -o libfoo.so '-Wl,-rpath,$ORIGIN'

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:

patchelf --remove-rpath libfoo.so

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.txt
  • app.c

See also: Examples overview ยท All COMPATIBLE cases ยท Category: Quality (Compatible).