Skip to content

Case 51: Protected Visibility (DEFAULT to PROTECTED)

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

Category: ELF / Policy | Verdict: ๐ŸŸข COMPATIBLE

What changes

Version hook_point ELF visibility
v1 STV_DEFAULT โ€” interposable via LD_PRELOAD or other .so
v2 STV_PROTECTED โ€” prevents interposition for references from within the defining shared object; does not affect external symbol resolution

Why this is NOT a binary ABI break

The symbol hook_point is still exported and resolvable in both versions. Existing binaries that call hook_point will find it and get correct results. No calling convention, type layout, or signature changes.

abicheck classifies this as COMPATIBLE because: - The symbol remains in .dynsym and is resolvable. - Function signature and behavior are unchanged. - Standard callers (via PLT/GOT) are unaffected.

What it does affect

  • Interposition is broken: LD_PRELOAD-based tooling (sanitizers, profilers, mock libraries) that overrides hook_point will no longer intercept calls made from within the library itself. The library's own compute() will always call its local hook_point, ignoring any preloaded override.
  • Plugin/hook systems: if consumers relied on overriding hook_point to customize library behavior, that contract is silently broken.
  • -Bsymbolic similarity: PROTECTED visibility has similar semantics to linking with -Bsymbolic โ€” internal references bind directly.

Code diff

 /* v1: DEFAULT โ€” interposable */
-int hook_point(int x) { return x * 2; }
+__attribute__((visibility("protected")))
+int hook_point(int x) { return x * 2; }

How to reproduce

# Build both versions
gcc -shared -fPIC -g old/lib.c -Iold -o libv1.so
gcc -shared -fPIC -g new/lib.c -Inew -o libv2.so

# Check visibility
readelf --dyn-syms libv1.so | grep hook_point
# โ†’ DEFAULT  hook_point
readelf --dyn-syms libv2.so | grep hook_point
# โ†’ PROTECTED hook_point

# Run abicheck
python3 -m abicheck.cli dump libv1.so -o /tmp/v1.json
python3 -m abicheck.cli dump libv2.so -o /tmp/v2.json
python3 -m abicheck.cli compare /tmp/v1.json /tmp/v2.json
# โ†’ COMPATIBLE (visibility metadata change noted)

Real Failure Demo

Severity: INFORMATIONAL

Scenario: app calls compute(5). Both versions return 11 โ€” no runtime difference for normal callers. The difference only surfaces with LD_PRELOAD interposition.

# Build old lib + 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 lib (PROTECTED hook_point)
gcc -shared -fPIC -g new/lib.c -Inew -o libfoo.so
./app
# โ†’ hook_point(5) = 10  โ† same result
# โ†’ compute(5)    = 11  โ† same result

# But with LD_PRELOAD override, behavior differs:
# v1: LD_PRELOAD hook overrides both direct calls AND calls from compute()
# v2: LD_PRELOAD hook overrides direct calls but NOT calls from within the library

Why INFORMATIONAL: Normal operation is identical. The concern is that interposition-dependent workflows (profiling, mocking, hot-patching) are silently broken for library-internal call paths.

Why runtime is COMPATIBLE (matches verdict)

PROTECTED symbols are still exported and resolved normally for external callers. The only behavioral change is in intra-library call resolution: the library always uses its own definition, preventing interposition. For standard consumers this is transparent.

Real-world example

GCC's -fno-semantic-interposition flag assumes interposed implementations have the same semantics โ€” showing that interposition is part of the ELF semantic model. Libraries that change from DEFAULT to PROTECTED visibility should document this as an interposition policy change.

References


Source files

  • CMakeLists.txt
  • app.c

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