Giao diện
🛡️ Sanitizers Security Scan
Stop chasing bugs manually. Let the compiler catch Use-After-Free, Buffer Overflow, và Data Races at runtime với near-zero cost.
Why Not Valgrind?
┌─────────────────────────────────────────────────────────────────────────┐
│ VALGRIND vs SANITIZERS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Tool Slowdown Coverage Production CI │
│ ──────────── ───────── ───────── ───────────── │
│ Valgrind 20-50x High ❌ Too slow │
│ AddressSanitizer 2x High ✅ Google uses in CI │
│ ThreadSanitizer 5-15x Races only ✅ Used in Chrome CI │
│ UBSan <1.5x UB only ✅ Always enabled │
│ │
│ HPN Policy: │
│ • ASan: ci-san-asan.yml → Run on every PR │
│ • TSan: ci-san-tsan.yml → Run on concurrent code changes │
│ • UBSan: ALWAYS enabled with -fsanitize=undefined │
│ │
└─────────────────────────────────────────────────────────────────────────┘AddressSanitizer (ASan) — Memory Bugs
What ASan Catches
- Use-after-free — Dangling pointer access
- Heap buffer overflow — Out-of-bounds write/read
- Stack buffer overflow — Local array overflow
- Use-after-return — Returning pointer to local
- Double-free — Freeing memory twice
- Memory leaks — With
-fsanitize=leak
Lab: Catching Use-After-Free
cpp
// bug.cpp — A nasty dangling pointer bug
#include <iostream>
#include <memory>
int main() {
int* ptr = new int(42);
delete ptr;
// ❌ USE-AFTER-FREE: ptr is dangling!
std::cout << "Value: " << *ptr << std::endl;
return 0;
}Compile with ASan
bash
# Enable ASan
g++ -fsanitize=address -g -o bug bug.cpp
# Or with Clang (recommended)
clang++ -fsanitize=address -g -o bug bug.cpp
# Run
./bugASan Output (Colorful!)
=================================================================
==12345==ERROR: AddressSanitizer: heap-use-after-free on address
0x602000000010 at pc 0x000000401234 bp 0x7ffd12345670 sp 0x7ffd12345668
READ of size 4 at 0x602000000010 thread T0
#0 0x401233 in main bug.cpp:10
#1 0x7f1234567890 in __libc_start_main
#2 0x401099 in _start
0x602000000010 is located 0 bytes inside of 4-byte region
[0x602000000010,0x602000000014)
freed by thread T0 here:
#0 0x7f12345abcde in operator delete(void*)
#1 0x401200 in main bug.cpp:6
previously allocated by thread T0 here:
#0 0x7f12345bcdef in operator new(unsigned long)
#1 0x4011f0 in main bug.cpp:5
SUMMARY: AddressSanitizer: heap-use-after-free bug.cpp:10 in main
=================================================================
]💡 READING ASAN OUTPUT
- Type of bug:
heap-use-after-free - Where accessed:
bug.cpp:10 - Where freed:
bug.cpp:6 - Where allocated:
bug.cpp:5
ASan tells you the COMPLETE story!
How ASan Works: Shadow Memory
┌─────────────────────────────────────────────────────────────────────────┐
│ SHADOW MEMORY CONCEPT │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ APPLICATION MEMORY SHADOW MEMORY (1/8 size) │
│ ───────────────────── ──────────────────────── │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ │
│ │ 8 bytes app mem │ ─────────► │ 1 byte shadow │ │
│ └───────────────────┘ └───────────────────┘ │
│ │
│ Shadow byte values: │
│ • 0x00: All 8 bytes accessible │
│ • 0xNN: First N bytes accessible (1-7) │
│ • 0xfa: Heap left redzone │
│ • 0xfb: Heap right redzone │
│ • 0xfd: Freed heap memory │
│ • 0xf1: Stack left redzone │
│ │
│ On every memory access: │
│ 1. Compute shadow address: (addr >> 3) + offset │
│ 2. Check shadow byte │
│ 3. If poisoned → Report error │
│ │
│ Cost: ~2x slowdown (vs 20x for Valgrind) │
│ │
└─────────────────────────────────────────────────────────────────────────┘ThreadSanitizer (TSan) — Data Races
What TSan Catches
- Data race — Two threads access same memory, at least one writes
- Use-after-destruction — Destroyed mutex/condition_variable
- Thread leaks — Threads not joined
Lab: Catching Data Race
cpp
// race.cpp
#include <thread>
#include <iostream>
int counter = 0; // Shared, no synchronization!
void Increment() {
for (int i = 0; i < 100000; ++i) {
counter++; // ❌ DATA RACE!
}
}
int main() {
std::thread t1(Increment);
std::thread t2(Increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
// Expected: 200000, Actual: Random!
}Compile with TSan
bash
# Cannot combine with ASan!
clang++ -fsanitize=thread -g -o race race.cpp
./raceTSan Output
==================
WARNING: ThreadSanitizer: data race (pid=12345)
Write of size 4 at 0x000000601040 by thread T2:
#0 Increment() race.cpp:9
#1 void std::__invoke_impl<void, void (*)()>...
Previous write of size 4 at 0x000000601040 by thread T1:
#0 Increment() race.cpp:9
#1 void std::__invoke_impl<void, void (*)()>...
Location is global 'counter' of size 4 at 0x000000601040
SUMMARY: ThreadSanitizer: data race race.cpp:9 in Increment()
==================The Fix
cpp
#include <atomic>
std::atomic<int> counter{0}; // ✅ Thread-safe!
void Increment() {
for (int i = 0; i < 100000; ++i) {
counter.fetch_add(1, std::memory_order_relaxed);
}
}UndefinedBehaviorSanitizer (UBSan)
What UBSan Catches
- Signed integer overflow
- Null pointer dereference
- Misaligned pointer access
- Out-of-bounds array index
- Division by zero
bash
clang++ -fsanitize=undefined -g -o ub ub.cpp
# Can combine with ASan!
clang++ -fsanitize=address,undefined -g -o both both.cppCMake Integration
cmake
# CMakeLists.txt
option(ENABLE_ASAN "Enable AddressSanitizer" OFF)
option(ENABLE_TSAN "Enable ThreadSanitizer" OFF)
option(ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer" OFF)
if(ENABLE_ASAN)
message(STATUS "AddressSanitizer enabled")
add_compile_options(-fsanitize=address -fno-omit-frame-pointer)
add_link_options(-fsanitize=address)
endif()
if(ENABLE_TSAN)
message(STATUS "ThreadSanitizer enabled")
add_compile_options(-fsanitize=thread)
add_link_options(-fsanitize=thread)
endif()
if(ENABLE_UBSAN)
message(STATUS "UndefinedBehaviorSanitizer enabled")
add_compile_options(-fsanitize=undefined)
add_link_options(-fsanitize=undefined)
endif()
# Usage:
# cmake -B build -DENABLE_ASAN=ON
# cmake --build buildGitHub Actions CI
yaml
# .github/workflows/sanitizers.yml
name: Sanitizers
on: [push, pull_request]
jobs:
asan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with ASan
run: |
cmake -B build -DENABLE_ASAN=ON
cmake --build build
- name: Run Tests
run: cd build && ctest --output-on-failure
tsan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build with TSan
run: |
cmake -B build -DENABLE_TSAN=ON
cmake --build build
- name: Run Tests
run: cd build && ctest --output-on-failureBest Practices
┌─────────────────────────────────────────────────────────────────────────┐
│ SANITIZER BEST PRACTICES │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ✅ DO │
│ ───── │
│ • Run ASan in CI on every PR │
│ • Use TSan for multithreaded code │
│ • Always enable UBSan (low overhead) │
│ • Compile with -g for readable stack traces │
│ • Set ASAN_OPTIONS for more info │
│ │
│ ❌ DON'T │
│ ─────── │
│ • Don't combine ASan + TSan (incompatible) │
│ • Don't deploy with sanitizers (2-15x slowdown) │
│ • Don't ignore sanitizer warnings │
│ • Don't use -O0 with sanitizers (slow + may hide bugs) │
│ │
│ 🔧 ENVIRONMENT VARIABLES │
│ ───────────────────────── │
│ ASAN_OPTIONS="detect_leaks=1:halt_on_error=0" │
│ TSAN_OPTIONS="second_deadlock_stack=1" │
│ UBSAN_OPTIONS="print_stacktrace=1" │
│ │
└─────────────────────────────────────────────────────────────────────────┘