An LLVM-based sanitizer that detects use of uninitialized stack pointers at runtime.
Dereferencing an uninitialized pointer is undefined behavior and a common source of bugs, especially in C codebases. Existing tools have limitations:
- Compiler warnings (
-Wuninitialized): Static analysis — misses conditional paths and inter-procedural cases - MSan (MemorySanitizer): Tracks uninitialized values broadly, but with high overhead and no pointer-specific reporting
- Valgrind: Runtime detection but with 10-20x slowdown
UninitPtrSanitizer focuses specifically on pointer-type stack variables, providing precise detection with minimal overhead.
- LLVM instrumentation pass scans every
allocaof pointer type - Shadow array (one byte per pointer slot) tracks UNINIT/INIT state
- On every
storeto the pointer → mark INIT - On every
load+ dereference → check shadow; if UNINIT, report and abort
alloca i32* %p → __upsan_alloc(slot_id) [shadow = UNINIT]
↓
store i32* %val, i32** %p → __upsan_init(slot_id) [shadow = INIT]
↓
%v = load i32*, i32** %p
%x = load i32, i32* %v → __upsan_check(slot_id, file, line, func, var)
↓ ↓ (if UNINIT)
report + abort
================================================================
UninitPtrSanitizer: use of uninitialized pointer
================================================================
Variable: p
Location:
test/test_uninit_deref.c:12 in main()
Backtrace:
#0 build/test_uninit_deref(main+0x52) [0x56278a136212]
#1 /lib/x86_64-linux-gnu/libc.so.6(+0x2a1ca) [0x7ea579c2a1ca]
================================================================
- LLVM/Clang 15+ (tested with 18)
- Linux
make CC=clang-18 CXX=clang++-18Produces:
build/UPSanPass.so— LLVM instrumentation passbuild/libupsan_rt.a— Runtime library
dnf install llvm-devel clang
make CC=clang CXX=clang++clang-18 -g -O0 \
-fpass-plugin=build/UPSanPass.so \
-o my_program my_program.c \
-Lbuild -lupsan_rt -lpthread -rdynamic
./my_programmake CC=clang-18 CXX=clang++-18 test| Test | Description | Expected |
|---|---|---|
test_clean |
All pointers initialized | Pass (no error) |
test_uninit_deref |
Direct dereference of uninit pointer | Caught |
test_conditional |
Pointer init in one branch, used unconditionally | Caught |
test_struct_ptr |
Struct member pointer (out of alloca scope) | Not caught (known limitation) |
Detected:
- Stack pointer variables (
allocaof pointer type) - Conditional initialization paths
- Passing uninitialized pointer to function calls
Not detected (current scope):
- Struct member pointers (not direct
alloca) - Heap-allocated pointer arrays
- Global pointer variables
These are potential extensions for future work.
- Scans all
AllocaInstwhere allocated type is pointer - Assigns each a unique compile-time
slot_id - Inserts
__upsan_alloc(id)after alloca - Inserts
__upsan_init(id)before every store to the pointer - Inserts
__upsan_check(id, file, line, func, var)before load+dereference - Dereference detection: checks if loaded value is used as pointer operand in load/store/GEP/call
- Shadow array:
uint8_t[]indexed by slot_id (0=UNINIT, 1=INIT) - Allocated once at
main()entry via__upsan_init_runtime(max_slots) - Check is a single array lookup — minimal overhead
- Colored error report with backtrace on detection
- PreciseLeakSanitizer — LLVM pass-based sanitizer pattern
- MemorySanitizer — Shadow memory approach for uninitialized values
MIT