Skip to content

suunj/UninitPtrSanitizer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

UninitPtrSanitizer

An LLVM-based sanitizer that detects use of uninitialized stack pointers at runtime.

Problem

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.

Solution

  1. LLVM instrumentation pass scans every alloca of pointer type
  2. Shadow array (one byte per pointer slot) tracks UNINIT/INIT state
  3. On every store to the pointer → mark INIT
  4. On every load + dereference → check shadow; if UNINIT, report and abort

Architecture

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

Example Output

================================================================
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]

================================================================

Requirements

  • LLVM/Clang 15+ (tested with 18)
  • Linux

Build

make CC=clang-18 CXX=clang++-18

Produces:

  • build/UPSanPass.so — LLVM instrumentation pass
  • build/libupsan_rt.a — Runtime library

Fedora/RHEL

dnf install llvm-devel clang
make CC=clang CXX=clang++

Usage

clang-18 -g -O0 \
  -fpass-plugin=build/UPSanPass.so \
  -o my_program my_program.c \
  -Lbuild -lupsan_rt -lpthread -rdynamic

./my_program

Run Tests

make 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)

Scope & Limitations

Detected:

  • Stack pointer variables (alloca of 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.

How It Works

LLVM Pass (pass/UPSanPass.cpp)

  • Scans all AllocaInst where 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

Runtime (runtime/upsan_rt.c)

  • 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

Inspired By

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors