Case 33: Pointer Level Change¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux, macOS, Windows |
| Flags | ABI break, API break |
Detected ChangeKinds |
param_pointer_level_changed |
| Source files | examples/case33_pointer_level/ |
Category: Function Signature | Verdict: ๐ด BREAKING
Verdict and consumer impact¶
process()'s parameter and get_buffer()'s return type both gain an
extra level of pointer indirection (int* โ int**). A caller compiled
against v1 passes a raw int* โ v2's process() dereferences it as
int**, treating the pointed-to integer's value as a memory address.
Similarly, get_buffer() now returns a pointer-to-pointer that a v1
caller still treats as a flat int* buffer. Either mismatch is an
immediate segfault (dereferencing an unmapped "address" formed from
ordinary integer data) or silent memory corruption. Recompilation is
mandatory.
Old/new diff¶
| v1.h | v2.h |
|---|---|
void process(int *data); |
void process(int **data); |
int *get_buffer(void); |
int **get_buffer(void); |
abicheck command¶
gcc -shared -fPIC -g v1.c -o libfoo_v1.so
gcc -shared -fPIC -g v2.c -o libfoo_v2.so
abicheck compare libfoo_v1.so libfoo_v2.so
Expected abicheck finding¶
Verdict: BREAKING (exit 4)
- func_return_changed: Return type changed: get_buffer (int * -> int * *)
> Callers expect the old return type layout in registers/stack;
misinterpretation causes data corruption.
- func_params_changed: Parameters changed: process (int * -> int * *)
> Callers push arguments with the old layout; callee reads wrong data
from stack/registers.
- return_pointer_level_changed: Return pointer level changed: get_buffer (depth 1 -> 2)
- param_pointer_level_changed: Parameter pointer level changed: process param data (depth 1 -> 2)
Minimum evidence¶
min_evidence: L1 โ DWARF's DW_TAG_pointer_type chain records how many
levels of indirection wrap a function's parameter or return type for both
binaries, so the depth-1-to-2 change is visible directly from debug info;
-g alone (no public headers) is enough.
Why abicheck catches it¶
abicheck walks each parameter's and return value's pointer-type DIE chain
and counts indirection depth. process's data parameter and
get_buffer's return value both go from one DW_TAG_pointer_type layer
to two, which is reported both as a generic signature change
(func_params_changed/func_return_changed) and via the dedicated
pointer-level detector that names the exact depth change.
Runtime failure demonstration¶
Severity: CRITICAL
Scenario: app compiled against v1 (int* parameter/return) runs
against v2, which reads/returns one extra level of indirection.
# Build old library + app
gcc -shared -fPIC -g v1.c -o libv1.so
gcc -g app.c -I. -L. -lv1 -Wl,-rpath,. -o app
./app
# โ process(&val) succeeded, val = 42
# โ get_buffer()[0] = 99
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libv1.so
./app
# โ Segmentation fault (core dumped)
Why CRITICAL: v2's process() performs **data, treating the integer
value 42 the caller passed as a memory address and dereferencing it โ
almost certainly an unmapped page, causing an immediate SIGSEGV. Even when
the dereferenced address happens to be mapped, get_buffer() returning a
pointer-to-pointer that a v1 caller indexes as a flat array reads
attacker/caller-uncontrolled memory as data.
Safe redesign¶
Never change a public function's parameter or return pointer depth in
place โ it's indistinguishable from any other signature change at the
symbol-name level (C has no overloading) and silently miscompiles callers.
Introduce a new function with a name that reflects the new signature
(process_indirect, get_buffer_ref) and deprecate the old one.
Real-world example: APIs that evolve from "caller owns a single
buffer" to "library owns a resizable/relocatable buffer" often make this
exact change (T* โ T** so the library can update the caller's pointer
in place) without renaming the function โ a classic source of hard-to-
diagnose crash reports after a routine library upgrade.
Cross-tool comparison¶
abidw --out-file v1.xml libfoo_v1.so
abidw --out-file v2.xml libfoo_v2.so
abidiff v1.xml v2.xml
echo "exit: $?"
References¶
Source files¶
CMakeLists.txtapp.cv1.cv1.hv2.cv2.h
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.