Skip to content

Case 51: Protected Visibility (DEFAULT to PROTECTED)

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Quality (Compatible)
Platforms Linux
Flags โ€”
Detected ChangeKinds symbol_elf_visibility_changed
Source files examples/case51_protected_visibility/

Category: Quality | Verdict: ๐ŸŸข COMPATIBLE

Verdict and consumer impact

Existing binaries that call hook_point() keep working unmodified โ€” the symbol is still exported and resolves normally for external callers. The only effect is on interposition: with STV_PROTECTED, the library's own internal calls to hook_point() always bind to its local definition, even under LD_PRELOAD. Tooling that relies on overriding hook_point() from outside the library (sanitizers, profilers, mocks) silently stops intercepting calls made from within the library.

Old/new diff

old/lib.c new/lib.c
int hook_point(int x) { return x * 2; } (DEFAULT visibility) __attribute__((visibility("protected")))
int hook_point(int x) { return x * 2; }

abicheck command

gcc -shared -fPIC -g old/lib.c -Iold -o libfoo_v1.so
gcc -shared -fPIC -g new/lib.c -Inew -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so

Expected abicheck finding

Verdict: COMPATIBLE (exit 0)

- symbol_elf_visibility_changed: ELF visibility changed: hook_point (default -> protected)
  > Symbol still exported and resolvable; intra-library calls to hook_point
    no longer honor LD_PRELOAD/interposition.

Minimum evidence

min_evidence: L0 โ€” ELF symbol visibility (STV_DEFAULT vs STV_PROTECTED) is recorded directly in .dynsym; no debug info or headers needed to see the change. (-g above is only there so DWARF-aware confidence is reported.)

Why abicheck catches it

abicheck reads each symbol's st_other visibility byte from the ELF dynamic symbol table and diffs it between versions โ€” a pure L0 ELF fact, independent of DWARF or headers.

Runtime failure demonstration

Severity: INFORMATIONAL โ€” no observable effect on existing binaries.

# Build old library + app
gcc -shared -fPIC -g old/lib.c -Iold -o libfoo.so
gcc -g app.c -Iold -L. -lfoo -Wl,-rpath,. -o app
./app
# โ†’ hook_point(5) = 10
# โ†’ compute(5)    = 11

# Swap in new library (no recompile)
gcc -shared -fPIC -g new/lib.c -Inew -o libfoo.so
./app
# โ†’ hook_point(5) = 10  โ† same result
# โ†’ compute(5)    = 11  โ† same result

Normal callers see identical output before and after the swap. The change only surfaces for LD_PRELOAD-based interposition of hook_point(), where v2's library-internal call from compute() no longer honors the preloaded override โ€” a policy concern, not an ABI break.

Safe redesign

If interposition of an internal helper is a supported extension point, document it explicitly and keep the symbol STV_DEFAULT. If protected visibility is intentional (performance, hardening), call it out in release notes so profiling/mocking tooling that depends on interposition isn't surprised.

Real-world example: GCC's -fno-semantic-interposition makes the same trade-off at compiler-flag granularity โ€” assuming interposed implementations are semantically equivalent so intra-TU calls can bind directly, trading interposability for codegen freedom.

References


Source files

  • CMakeLists.txt
  • app.c

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