Case 65: Symbol Version Removed¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break |
Detected ChangeKinds |
symbol_version_node_removed |
| Source files | examples/case65_symbol_version_removed/ |
Category: Symbol Versioning | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
The CRYPTO_1.0 symbol-version node is removed from v2 of the library.
Old binaries linked against crypto_hash@CRYPTO_1.0 record that version
requirement in their .gnu.version_r section. When the dynamic linker
loads v2 โ which only provides CRYPTO_2.0 โ it cannot satisfy the
CRYPTO_1.0 requirement and refuses to start the process. Tools like nm
or readelf -s still show crypto_hash in the symbol table, so the
breakage is invisible to a naive symbol-name check; only the version node
itself is gone. Recompilation (and re-linking against whatever version node
now provides crypto_hash) is mandatory for affected binaries.
Old/new diff¶
v1.c: .symver crypto_hash_v1,crypto_hash@CRYPTO_1.0 โ compat version
.symver crypto_hash_v2,crypto_hash@@CRYPTO_2.0 โ default version
v1.map: CRYPTO_1.0 { crypto_hash; };
CRYPTO_2.0 { crypto_hash; crypto_verify; } CRYPTO_1.0;
v2.c: (no .symver โ plain crypto_hash() definition) โ CRYPTO_1.0 gone!
v2.map: CRYPTO_2.0 { crypto_hash; crypto_verify; };
abicheck command¶
gcc -shared -fPIC -g v1.c -Wl,--version-script=v1.map -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -Wl,--version-script=v2.map -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- symbol_version_node_removed: Version node CRYPTO_1.0 was entirely removed
from the version script. Symbols previously under this node: crypto_hash.
Applications linked against CRYPTO_1.0 will get unresolved symbol errors.
> A version node (e.g. LIBFOO_1.0) was entirely removed from the version
script. Applications linked against symbols under that version node
will get unresolved symbol errors at load time.
Additions:
- func_added: New public function: crypto_hash
> New function available; existing binaries are unaffected.
The func_added finding is an artifact of comparing the plain (unversioned)
export name once the version wrapper is gone โ the symbol_version_node_removed
finding above is the one that carries the real break, and both
SYMBOL_VERSION_DEFINED_REMOVED and SYMBOL_VERSION_NODE_REMOVED detect
this same removal; abicheck's cross-detector deduplication keeps the more
specific symbol_version_node_removed.
Minimum evidence¶
min_evidence: L0 โ the ELF .gnu.version_d section alone records which
version nodes a library defines and which symbols belong to each; comparing
that section between v1 and v2 is enough, no debug info or headers needed.
Why abicheck catches it¶
abicheck parses the .gnu.version_d (version definitions) section of both
binaries into a node โ symbol-set mapping and diffs the node sets directly โ
the same class of authoritative, always-present ELF metadata that a plain
symbol removal (case01) is detected from, just one section over.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: compile app against v1 (links to crypto_hash@CRYPTO_1.0),
swap in v2 .so which removed the CRYPTO_1.0 version node.
# Build v1 library with both version nodes, and app
gcc -shared -fPIC -g v1.c -Wl,--version-script=v1.map -o libcrypto.so
gcc -g app.c -L. -lcrypto -Wl,-rpath,. -o app
./app
# โ hash("hello") = 99162322
# โ OK: crypto_hash@CRYPTO_1.0 resolved successfully
# Check version requirement recorded in the binary
readelf -V app | grep CRYPTO
# โ Name: CRYPTO_1.0 Flags: none Version: 4
# Swap in v2 library (CRYPTO_1.0 removed)
gcc -shared -fPIC -g v2.c -Wl,--version-script=v2.map -o libcrypto.so
./app
# โ ./app: ./libcrypto.so: version `CRYPTO_1.0' not found (required by ./app)
Why CRITICAL: the dynamic linker's version check is strict โ if the required version node doesn't exist in the loaded library, the process is killed immediately. This is a hard failure with a clear error message, but it still catches many library maintainers by surprise when they "clean up" old version nodes.
Safe redesign¶
Never remove a symbol version node from a shared library. Keep the old
.symver alias even once the implementation is identical
(__asm__(".symver old_impl,func@OLD_VER") can point the old version at the
new implementation), and only drop old version nodes alongside a SONAME
major-version bump that forces all consumers to re-link.
Real-world example: glibc maintains symbol versions going back to
GLIBC_2.0 (1997) โ its version script is append-only, because removing any
node would break every binary linked against that version across the entire
Linux ecosystem. OpenSSL 3.0 removed the OPENSSL_1.0.0 and OPENSSL_1.1.0
version nodes, which is why it required a SONAME change from
libssl.so.1.1 to libssl.so.3 โ all consumers had to be rebuilt.
Cross-tool comparison¶
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv1.mapv2.cv2.hv2.map
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.