Skip to content

Case 51: Protected Visibility (DEFAULT to PROTECTED)

Field Value
Verdict ๐ŸŸข COMPATIBLE
Category Quality (Compatible)
Platforms Linux
Flags โ€”
Detected ChangeKinds โ€”
Source files browse on GitHub

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

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