Rust Sanitizers and Miri
Purpose
Guide agents through runtime safety validation for Rust: ASan/TSan/MSan/UBSan via RUSTFLAGS, Miri for compile-time UB detection in unsafe code, and interpreting sanitizer reports.
Triggers
-
"How do I run AddressSanitizer on Rust code?"
-
"How do I use Miri to check my unsafe Rust?"
-
"How do I run ThreadSanitizer on a Rust program?"
-
"My unsafe Rust might have UB — how do I detect it?"
-
"How do I interpret a Rust ASan report?"
-
"Can I run Rust sanitizers on stable?"
Workflow
- Sanitizers in Rust (nightly required)
Rust sanitizers require nightly and a compatible platform:
Install nightly
rustup toolchain install nightly rustup component add rust-src --toolchain nightly
AddressSanitizer (Linux, macOS)
RUSTFLAGS="-Z sanitizer=address"
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu
ThreadSanitizer (Linux)
RUSTFLAGS="-Z sanitizer=thread"
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu
MemorySanitizer (Linux, requires all-instrumented build)
RUSTFLAGS="-Z sanitizer=memory -Zsanitizer-memory-track-origins"
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu
UndefinedBehaviorSanitizer
RUSTFLAGS="-Z sanitizer=undefined"
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu
-Zbuild-std rebuilds the standard library with the sanitizer, which is necessary for accurate results.
- Stable sanitizer workaround
For stable Rust, use the cross tool with a Docker image that has sanitizers pre-configured, or run cargo test inside a Docker container with a nightly image.
Alternatively, for simpler UB checking without nightly:
cargo-sanitize (wrapper)
cargo install cargo-sanitize cargo sanitize address
- Interpreting ASan output in Rust
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000050 READ of size 4 at 0x602000000050 thread T0 #0 0x401234 in myapp::module::function /src/main.rs:15 #1 0x401567 in myapp::main /src/main.rs:42
0x602000000050 is located 0 bytes after a 40-byte region allocated at: #0 0x... in alloc::alloc::alloc ... #1 0x... in myapp::create_buffer /src/main.rs:10
Rust-specific patterns:
ASan error Likely Rust cause
heap-buffer-overflow
unsafe slice access past bounds
use-after-free
unsafe pointer use after Vec realloc
stack-use-after-return
Returning reference to local
heap-use-after-free
Use after drop() or Box::from_raw
- Miri — interpreter for undefined behaviour
Miri interprets Rust MIR and detects UB that sanitizers might miss:
Install Miri (requires nightly)
rustup +nightly component add miri
Run tests under Miri
cargo +nightly miri test
Run specific test
cargo +nightly miri test test_name
Run a binary under Miri
cargo +nightly miri run
Run with Stacked Borrows model (strict aliasing)
MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test
Disable isolation (allow file I/O, randomness)
MIRIFLAGS="-Zmiri-disable-isolation" cargo +nightly miri test
- What Miri detects
// 1. Dangling pointer use unsafe { let x = Box::new(42); let ptr = Box::into_raw(x); let _ = Box::from_raw(ptr); // drop let _val = *ptr; // Miri: use of dangling pointer }
// 2. Invalid enum discriminant let x: u8 = 3; let e = unsafe { std::mem::transmute::<u8, MyEnum>(x) }; // Miri: enum value has invalid tag
// 3. Uninitialized memory read let uninit: MaybeUninit<u32> = MaybeUninit::uninit(); let val = unsafe { uninit.assume_init() }; // Miri: reading uninitialized bytes
// 4. Stacked borrows violation let mut x = 5u32; let ptr = &mut x as *mut u32; let _ref = &x; // shared reference unsafe { *ptr = 10; } // Miri: mutable access while shared borrow exists
// 5. Data races (with threads) // Miri simulates sequential execution and detects races via Stacked Borrows
- ThreadSanitizer for Rust
RUSTFLAGS="-Z sanitizer=thread"
RUST_TEST_THREADS=8
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu 2>&1 | head -50
TSan output:
WARNING: ThreadSanitizer: data race (pid=12345) Write of size 4 at 0x7f... by thread T2 (mutexes: write M1): #0 myapp::counter::increment src/counter.rs:10 Previous read of size 4 at 0x7f... by thread T1: #0 myapp::counter::get src/counter.rs:5
- Miri configuration via MIRIFLAGS
Flag Effect
-Zmiri-disable-isolation
Allow I/O, clock, randomness
-Zmiri-strict-provenance
Strict pointer provenance (stricter than LLVM)
-Zmiri-symbolic-alignment-check
Stricter alignment checking
-Zmiri-check-number-validity
Check float/int validity
-Zmiri-num-cpus=N
Simulate N CPUs (for concurrency)
-Zmiri-seed=N
Seed for random scheduling
-Zmiri-ignore-leaks
Suppress memory leak errors
-Zmiri-tag-raw-pointers
Track raw pointer provenance
- CI integration
GitHub Actions
-
name: Miri run: | rustup toolchain install nightly rustup +nightly component add miri cargo +nightly miri test env: MIRIFLAGS: "-Zmiri-disable-isolation"
-
name: ASan (nightly) run: | rustup component add rust-src --toolchain nightly RUSTFLAGS="-Z sanitizer=address"
cargo +nightly test -Zbuild-std
--target x86_64-unknown-linux-gnu
Related skills
-
Use skills/rust/rust-debugging for GDB/LLDB debugging of Rust panics
-
Use skills/runtimes/sanitizers for C/C++ sanitizer usage and comparison
-
Use skills/rust/rust-unsafe for unsafe Rust patterns and review checklist
-
Use skills/runtimes/fuzzing to generate inputs that trigger sanitizer errors