Case 170: Runtime Floor Raised (glibc Relink Drift)¶
| Field | Value |
|---|---|
| Verdict | 🟡 COMPATIBLE_WITH_RISK |
| Category | Risk |
| Classification | Rule |
| Platforms | Linux, macOS, Windows |
| Flags | — |
Detected ChangeKinds |
runtime_floor_raised, symbol_version_required_added |
| Source files | catalog/cases/case170_env_runtime_floor_raised/ |
| Rule family | env-runtime-floor-raised |
| Subject | ELF security hardening and deployment risk |
Category: Environment / Toolchain Drift | Verdict: 🟡 COMPATIBLE_WITH_RISK
Verdict and consumer impact¶
libdemo.so.1 exports the exact same two functions (demo_init,
demo_process) in both versions — nothing about the declared interface
changed. But v2 was relinked on a newer distro, so its imports rebind to a
newer glibc version node: __libc_start_main moves from @GLIBC_2.28 to
@GLIBC_2.34. Existing consumers on a modern system are unaffected; the
same binary simply refuses to load (version 'GLIBC_2.34' not found) on
anything older than glibc 2.34 — RHEL 8, Ubuntu 20.04, and similar. Whether
that's a problem depends entirely on deployment targets abicheck can't see
on its own.
Old/new diff¶
There is no source change; this is a toolchain/build-environment diff between the two shipped binaries' dynamic version requirements:
| old.abi.json | new.abi.json |
|---|---|
__libc_start_main@GLIBC_2.28 (max glibc floor: 2.28) |
__libc_start_main@GLIBC_2.34 (max glibc floor: 2.34) |
abicheck command¶
abicheck compare old.abi.json new.abi.json # default: no .abicheck.yml deployment: block
# .abicheck.yml declaring a floor >= 2.36:
# deployment:
# runtime_floors: {GLIBC: "2.36"}
abicheck compare old.abi.json new.abi.json # run with that config in the working dir
# .abicheck.yml declaring floor 2.28:
# deployment:
# runtime_floors: {GLIBC: "2.28"}
abicheck compare old.abi.json new.abi.json # run with that config in the working dir
Expected abicheck finding¶
Default (no deployment: block):
Verdict: COMPATIBLE_WITH_RISK (exit 0)
- symbol_version_required_added: New symbol version requirement: GLIBC_2.34 (from libc.so.6)
- runtime_floor_raised: Runtime floor raised for libc.so.6: GLIBC_2.28 -> GLIBC_2.34
(required by: __libc_start_main@GLIBC_2.34)
deployment.runtime_floors.GLIBC: "2.36" (e.g. Ubuntu 24.04+):
Verdict: COMPATIBLE (exit 0)
deployment.runtime_floors.GLIBC: "2.28" (e.g. RHEL 8):
Verdict: BREAKING (exit 4)
Minimum evidence¶
min_evidence: L0 — the ELF dynamic section's version-needed (verneed)
records list which symbol-version node each imported symbol requires; the
exported/imported symbol table alone (no debug info, no headers) is enough
to see the floor move.
Why abicheck catches it¶
abicheck reads each binary's verneed entries directly from L0 ELF
metadata and reports two granularities: the per-node fact
(symbol_version_required_added) and a roll-up
(runtime_floor_raised) that names the old→new deployment floor and the
importing symbol. The roll-up's evidence list is the actionable part — a
floor pulled up only by __libc_start_main (linked in by every binary) is
a pure relink artifact, while a real application-level symbol pulling the
floor up would mean the code genuinely started using a newer runtime API.
Real-world deployment scenario¶
This is what a CI job diffing two release builds of the same library
across a build-image upgrade sees — e.g. a package's builder migrating from
an older base image to a newer one (or a distro's compiler/glibc bump)
with zero source changes. The declared interface is untouched, but the
binary's minimum runtime silently rises, and only a build that pins (or
declares) its oldest supported runtime catches it before a customer on an
older system hits a load failure. .abicheck.yml's deployment: block is
exactly that declaration: pairing the observed floor against a stated
deployment target turns the open question into a pass/fail verdict, as the
two declared-floor runs above show.
Safe redesign¶
Pin the build image (or use an old-sysroot/manylinux-style toolchain) so
released binaries target the oldest glibc you actually support, rather than
whatever the CI runner happens to ship. Where that's impractical, declare
the real deployment floor in .abicheck.yml's deployment: block so drift
like this fails the build intentionally instead of surfacing as a support
ticket after release.
Cross-tool comparison¶
abidiff/abi-compliance-checker diff DWARF-carrying compiled binaries
for type/symbol changes; neither has a concept of a declared deployment
runtime floor or a deployment:-style target declaration, so there is no
equivalent reproduction of this finding with either tool.
Source files¶
env-newer.yamlenv-older.yamlnew.abi.jsonold.abi.json
See also: Compatibility Catalog · All COMPATIBLE_WITH_RISK cases · Category: Risk · Rule: Runtime floor raised · Subject: ELF security hardening and deployment risk.