rust-sanitizers-miri
Skill5 starsChanged 13 days agoBSD-3-ClauseView on GitHub
Use when running Miri or a sanitizer (ASan, ThreadSanitizer, MSan, HWASan) on Rust code, choosing between Stacked Borrows and Tree Borrows, reading a Miri or ASan report, stubbing FFI that Miri cannot execute, enabling Android MTE or Xcode sanitizers for a Rust library, running UBSan on a C dependency, or wiring these checks into CI. Not for writing or reviewing unsafe code itself; use `rust-unsafe`. Triggers on "miri", "MIRIFLAGS", "sanitizer", "-Zsanitizer", "TSan", "SEGV_MTESERR", "SEGV_MTEAERR".
In this repository46 other files it gives its agents.
- AGENTS.mdAGENTS.md
- cargo-workflowsSkill
- ffi-error-progress-cancelSkill
- memory-modelSkill
- rust-android-buildSkill
- rust-async-internalsSkill
- rust-borrow-semanticsSkill
- rust-callback-boundsSkill
- rust-cliSkill
- rust-code-styleSkill
- rust-compiler-errorsSkill
- rust-copy-on-writeSkill
- rust-crate-architectureSkill
- rust-crate-releaseSkill
- rust-databaseSkill
- rust-debuggingSkill
- rust-disciplineSkill
- rust-embedded-no-stdSkill
- rust-event-loop-stateSkill
- rust-hot-pathSkill
- rust-ios-buildSkill
- rust-iterator-implSkill
- rust-jniSkill
- rust-lintsSkill
- rust-macrosSkill
- rust-native-linkingSkill
- rust-networkingSkill
- rust-observabilitySkill
- rust-panic-safetySkill
- rust-pattern-semanticsSkill
- rust-performanceSkill
- rust-pin-projectionSkill
- rust-portSkill
- rust-reviewSkill
- rust-securitySkill
- rust-send-syncSkill
- rust-serdeSkill
- rust-swift-ffiSkill
- rust-tddSkill
- rust-test-toolsSkill
- rust-type-erasureSkill
- rust-unsafeSkill
- rust-varianceSkill
- rust-wasmSkill
- uniffi-boundarySkill
- uniffi-packaging-versioningSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
24 rules. About 0 are common, 10 are distinctive, and 14 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈10
Distinctive · estimate
This author's own choices. Worth a look.
14
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 27
Read references/miri-ub-patterns.md ("Coverage decision table", "Sanitizer comparison") when you decide which tool runs on each crate of a …
- Very specific: line 110
Never silence that error with -Cunsafe-allow-abi-mismatch=sanitizer.
- Very specific: line 136
Keep secrets out of any Miri job that writes a target/ cache a pull request can read.
- Very specific: line 144
Keep the Miri test set small and deterministic.
- Very specific: line 162
Read references/miri-ub-patterns.md when a Miri run fails and you need the shape of the defect and its fix.
- Distinctive: line 173
Run the default model, Stacked Borrows (SB), first.
- Distinctive: line 174
Add a Tree Borrows (TB) run as more evidence on a crate with hand-written raw-pointer code.
- Distinctive: line 178
Treat a failure under either model as a finding.
- Distinctive: line 185
Run Miri over the Rust logic while the foreign call is replaced or skipped.
- Distinctive: line 214
If the real function returns a pointer that the caller dereferences, the stub returns a pointer into a real allocation.
- Distinctive: line 217
If the real function stores a pointer, the stub stores it too, and the stub of each later call that uses it dereferences it.
- Very specific: line 220
Do not let a stub hide the UB that you want to find.
- Very specific: line 224
Keep the stub next to the real declaration, in the same module, so it cannot drift from the signature.
- Very specific: line 231
Put a comment with the reason on each #[cfg_attr(miri, ignore)], and name the job that covers the path instead.
- Distinctive: line 242
Every path that a stub, a skip or an exclusion removes from Miri needs an ASan run on the host, or a HWASan or MTE run on a device.
- Very specific: line 258
Read references/miri-ub-patterns.md ("Box plus FFI aliasing") when you write these stubs for a registration API.
- Very specific: line 270
Use Pin<Box<T>> only when T has a pinning invariant.
- Very specific: line 311
Read references/miri-flags-and-ci.md when you need another flag, for example -Zmiri-seed=N to reproduce one failing seed.
- Very specific: line 327
Read frame #0. It names the access, not always the defect.
- Distinctive: line 328
Read the allocation and free stacks that ASan prints below the access.
- Distinctive: line 330
If the path is pure Rust, write a Miri test for it.
- Very specific: line 340
Read references/platform-sanitizers.md when you build for a device.
- Very specific: line 346
Read references/miri-flags-and-ci.md when you write the CI jobs.
- Distinctive: line 349
Treat a Miri failure as a build failure.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Rust Sanitizers and Miri
1. Select the tool
Select the tool from the bug class. Each tool finds its own class and misses the others.
| Bug class | Tool | Does not find it |
| Aliasing breach, invalid value, provenance error in Rust | Miri | ASan, HWASan, MTE: they do not model Rust rules |
| Heap overflow, use-after-free, double free at runtime | ASan, HWASan or MTE | Miri, when the path reaches foreign code |
| Data race between threads | TSan, or Miri with -Zmiri-many-seeds | ASan |
| Read of uninitialized memory | Miri, or MSan with every object instrumented | ASan |
| Integer overflow or null dereference in a C or C++ dependency | UBSan: clang -fsanitize=undefined on that dependency | Miri (does not run C); rustc (has no UBSan option) |
| Memory error inside a C or C++ dependency | ASan, HWASan or MTE | Miri |
Miri knows the Rust rules but cannot execute foreign code. Sanitizers execute
foreign code but do not know the Rust aliasing model. For a crate that has
unsafe code and FFI, run both.
Read references/miri-ub-patterns.md ("Coverage decision table", "Sanitizer
comparison") when you decide which tool runs on each crate of a workspace, or
when you need overhead figures.
A green run does not prove the absence of undefined behaviour (UB). Every tool checks only the paths that the tests execute. A clean Miri run says nothing about a stubbed or skipped path. A clean sanitizer run says nothing about uninstrumented code or about the Rust aliasing rules.
2. Review gates
Apply these gates before you approve a change that adds or edits unsafe.
- A test exercises the new
unsafepath. cargo +nightly miri test --lockedpasses on that test, or the test has#[cfg_attr(miri, ignore)]and a comment that gives the reason.- The strict-provenance job passes, or the code exposes provenance on purpose and runs in the separate job (section 9).
- Raw-pointer code also passes under
-Zmiri-tree-borrows. - Byte-parsing code passes under
-Zmiri-symbolic-alignment-check. - Each new
#[cfg(miri)]stub keeps the signature, the return domain and any stored pointer of the real function. - An ASan, HWASan or MTE run covers every path that Miri skips.
- New concurrency is covered by TSan, by Miri with
-Zmiri-many-seeds, or byloom.
3. Failure triage
Miri messages below were measured on nightly 2026-05-15. Match on the quoted fragment. Tags and allocation ids change with the program, the toolchain, the seed and the flags.
| Symptom | Probable cause | Next action |
Miri: unsupported operation: can't call foreign function | The test reaches code that Miri cannot interpret | Apply section 7 |
Miri: not available when isolation is enabled | The test reads a host resource, for example SystemTime::now or a file | Add -Zmiri-disable-isolation to that job, or inject the value in the test |
Miri: allocN has been freed, so this pointer is dangling | A pointer kept across a reallocation or a drop | Re-derive the pointer after each operation that can reallocate |
Miri: in-bounds pointer arithmetic failed | add or offset past the end of the allocation | Check the length before the arithmetic |
Miri: constructing invalid value ... expected a valid enum tag or expected a boolean | A transmute or read produced a value that no variant or bool uses | Decode with TryFrom and return an error |
Miri: accessing memory based on pointer with alignment ... is required, only on some seeds | A misaligned read through a raw pointer | Use read_unaligned or from_le_bytes; add -Zmiri-symbolic-alignment-check |
Miri: memory is uninitialized or encountered uninitialized memory | A read of MaybeUninit before every byte was written | Write every byte first, or use a zero-filled buffer |
Miri: does not exist in the borrow stack (SB) or is forbidden (TB) | An aliasing violation | Section 6, section 8, and references/miri-ub-patterns.md |
Miri: integer-to-pointer casts and `ptr::with_exposed_provenance` are not supported | The code rebuilds a pointer from an integer under -Zmiri-strict-provenance | Keep the original pointer, or use with_addr or map_addr. If exposure is required, move the test to the separate job |
Miri: dangling pointer (it has no provenance) | A pointer made from an integer that was never exposed | Keep the original pointer, or use with_addr |
Miri: memory leaked: allocN | An allocation that no static can reach: Box::leak, mem::forget, or a pointer held only by a thread still running at exit or by foreign state | Store an intended process-lifetime value in a static (OnceLock or LazyLock), which Miri does not report, or add -Zmiri-ignore-leaks to that job only |
| Miri passes, ASan fails | The defect is in foreign code that Miri stubbed or skipped | Debug with ASan and read the allocation stack |
| ASan passes, Miri fails | An aliasing or provenance breach that did not corrupt memory on this run | Fix it. It is UB, and the optimizer can act on it later |
| MSan reports uninitialized reads in a dependency | Not every object was built with MSan | Rebuild every dependency with MSan, or use Miri |
| TSan reports a race inside an atomics-based structure | An ordering is too weak, or the code synchronizes with a fence that TSan does not model | Confirm with Miri -Zmiri-many-seeds or loom before you change an ordering; see the memory-model skill |
mixing `-Zsanitizer` will cause an ABI mismatch in crate | A crate or std was built without the sanitizer | Add -Zbuild-std; for doctests, set RUSTDOCFLAGS to the RUSTFLAGS value |
Doctests fail to link with undefined __asan_* symbols | RUSTDOCFLAGS lacks the -Zsanitizer flag | Set RUSTDOCFLAGS to the RUSTFLAGS value |
| rustc aborts while it expands a proc macro under a sanitizer | --target is missing, so the proc macro was instrumented | Pass --target "$HOST" |
4. Run a sanitizer on the host
-Zsanitizer is unstable as of Rust 1.98.1, so every sanitizer run needs
nightly.
rustup toolchain install nightly --component rust-src
HOST="$(rustc +nightly -vV | sed -n 's/^host: //p')"
# AddressSanitizer (Linux, macOS)
RUSTFLAGS="-Zsanitizer=address" RUSTDOCFLAGS="-Zsanitizer=address" \
cargo +nightly test --locked -Zbuild-std --target "$HOST"
# ThreadSanitizer (Linux, macOS)
RUSTFLAGS="-Zsanitizer=thread" RUSTDOCFLAGS="-Zsanitizer=thread" \
cargo +nightly test --locked -Zbuild-std --target "$HOST"
# MemorySanitizer (Linux; every linked object must be instrumented)
RUSTFLAGS="-Zsanitizer=memory -Zsanitizer-memory-track-origins" \
RUSTDOCFLAGS="-Zsanitizer=memory -Zsanitizer-memory-track-origins" \
cargo +nightly test --locked -Zbuild-std --target "$HOST"Rules (build failures and ASan defaults measured on nightly 2026-05-15,
aarch64-apple-darwin):
- Pass
-Zbuild-std. The prebuilt standard library is not instrumented. Without the rebuild, TSan stops withmixing `-Zsanitizer` will cause an ABI mismatch in crate, ASan leaves std unchecked, and MSan reports false positives. - Never silence that error with
-Cunsafe-allow-abi-mismatch=sanitizer. The mixed build misses errors in the uninstrumented crates and can report false positives. Fix the flags so every crate is instrumented. - Pass
--target, also for the host. Then Cargo keepsRUSTFLAGSoff build scripts and proc macros. An instrumented proc macro can abort rustc. - Set
RUSTDOCFLAGSto the same-Zsanitizervalue, or every doctest fails to build (section 3). - On macOS, set
ASAN_OPTIONS=detect_stack_use_after_return=1:detect_leaks=1. Without it, ASan missesstack-use-after-returnand does not check leaks. Linux enables both by default, so an intentional leak fails a Linux ASan run. Setdetect_leaks=0only for that job. - Build every C and C++ dependency with clang
-fsanitize=memoryfor an MSan run. Otherwise do not trust an MSan report. - Expect TSan false positives on
std::sync::atomic::fenceand on synchronization in inline assembly. TSan does not model either one.
5. Run Miri
rustup +nightly component add miri
cargo +nightly miri test --locked # whole suite
cargo +nightly miri test --locked <test_filter> # one testNightly and cache rules:
- Keep secrets out of any Miri job that writes a
target/cache a pull request can read. Miri nightlies before 2026-09-22 wrote every environment variable intotarget/. If such a job ran with secrets, clear the cache and rotate the secrets (Rust blog, 2026-09-21). - Pin a nightly date if a Miri regression blocks the pipeline. Pin 2026-09-22 or later. Miri diagnostics change with the nightly.
Miri interprets every operation, so it runs much slower than a native test.
Keep the Miri test set small and deterministic. Gate a large-input or
long-running test with #[cfg_attr(miri, ignore)], or shrink its input under
cfg(miri).
Miri also reports data races and leaks at exit. It reports an invalid value
where the code produces it, at the transmute or read, not at a later use. It
emulates some weak-memory effects, so an atomic load can return
an outdated value, but that emulation is not complete.
Miri cannot execute a foreign function that has no Miri shim. This includes
every C or C++ entry point behind a -sys crate, JNI calls, generated UniFFI
scaffolding, and inline assembly. Miri shims a subset of the platform API,
including files (with -Zmiri-disable-isolation), threads, and epoll and
eventfd on Linux targets. System API support varies between targets. If a shim
is missing, try --target x86_64-unknown-linux-gnu before you stub.
-Zmiri-native-lib calls real native code, but it is experimental and unsound.
A run through it is not evidence for the FFI path.
Read references/miri-ub-patterns.md when a Miri run fails and you need the
shape of the defect and its fix. It shows each UB class with the message Miri
prints.
6. Aliasing model policy
cargo +nightly miri test --locked # Stacked Borrows (default)
MIRIFLAGS="-Zmiri-tree-borrows" cargo +nightly miri test --locked # Tree Borrows- Run the default model, Stacked Borrows (SB), first. The SB run is the gate.
- Add a Tree Borrows (TB) run as more evidence on a crate with hand-written raw-pointer code. Never make TB the only gate. The Miri README calls TB "even more experimental than Stacked Borrows", and code it accepts today "might be declared UB in the future".
- Treat a failure under either model as a finding.
- Fix an SB failure even when TB passes. TB accepts some patterns that SB
rejects, for example a
&mutreborrow that is written after a read through its parent raw pointer.
7. Stub an FFI dependency that Miri cannot execute
Run Miri over the Rust logic while the foreign call is replaced or skipped. Select the strategy from the shape of the dependency.
| FFI situation | Strategy |
extern "C" block that you declare in your own crate | Gate the block with #[cfg(not(miri))]. Add a #[cfg(miri)] stub with the same signature. |
Inline asm! or global_asm! | Add a pure-Rust fallback behind #[cfg(miri)]. Keep both paths under one test. |
| Third-party crate that links C or C++ | You cannot stub it. Exclude the crate from the Miri run, or gate the tests that reach it. |
| Generated FFI scaffolding, for example a UniFFI or JNI binding layer | Do not stub the generated code. Gate the tests that cross the boundary. |
| Test that needs a live host runtime, for example a JVM, a GPU driver or a database | Gate the test with #[cfg_attr(miri, ignore)]. Do not stub. |
Platform syscall through libc with a simple return value | Stub it behind #[cfg(miri)] and return the success value. |
Stub a foreign function that you declare
#[cfg(not(miri))]
unsafe extern "C" {
fn platform_specific_call(fd: i32) -> i32;
}
#[cfg(miri)]
unsafe fn platform_specific_call(_fd: i32) -> i32 {
0 // Deterministic success value for the interpreter.
}The stub must keep the signature, the safety contract and the return domain of the real function:
- If the real function returns a pointer that the caller dereferences, the stub returns a pointer into a real allocation. A null stub moves the defect instead of removing it.
- If the real function stores a pointer, the stub stores it too, and the stub of each later call that uses it dereferences it. Otherwise Miri cannot see an alias against the stored pointer (section 8).
- Do not let a stub hide the UB that you want to find. A stub that always
returns
0for a function whose error path frees a buffer removes the test you need.
Keep the stub next to the real declaration, in the same module, so it cannot
drift from the signature. Use the built-in cfg(miri), not a miri Cargo
feature: Miri sets the cfg itself, and a feature can be enabled by accident in
a normal build.
Skip a test or exclude a crate
Put a comment with the reason on each #[cfg_attr(miri, ignore)], and name
the job that covers the path instead.
# Select the crates that Miri can interpret.
cargo +nightly miri test --locked -p my-core -p my-parser
# Or run the workspace and exclude the crates that reach foreign code.
cargo +nightly miri test --locked --workspace --exclude my-ffiEvery path that a stub, a skip or an exclusion removes from Miri needs an ASan
run on the host, or a HWASan or MTE run on a device. A cargo +nightly careful
test run without -Zcareful-sanitizer is not a substitute: it adds std debug
assertions, not memory-error detection.
8. A Box and a pointer that foreign code keeps
A Box<T> asserts unique access while it is live. If foreign code stores a
*mut T taken from the Box, and Rust then writes through the Box, the
stored pointer is invalid. A later foreign access (read or write) through it
is UB.
Miri sees this only when Miri-executed code uses the stored pointer after the
Box access. Make the #[cfg(miri)] stubs keep the pointer and read through
it on a later call, as the foreign side does. Then both SB and TB report the
read. With a stub that ignores the pointer, neither model reports anything.
Read references/miri-ub-patterns.md ("Box plus FFI aliasing") when you
write these stubs for a registration API. It has the full stubs and test.
Correct patterns:
- Transfer ownership with
Box::into_rawand never use the originalBoxagain. Access the value only through the raw pointer. Recover it withBox::from_rawexactly once, after the foreign side unregisters it and no foreign call or callback can still use the pointer. An unregister call can return while a callback on a foreign thread still runs. - For a synchronous foreign borrow, do not access or reborrow the value until the call returns.
- Use
Pin<Box<T>>only whenThas a pinning invariant. It keeps the address stable. It does not permit aliasing or concurrent foreign access.
9. Miri jobs and flag hazards
# Gate: default model and strict provenance.
MIRIFLAGS="-Zmiri-strict-provenance" cargo +nightly miri test --locked
# Byte parsers and raw-pointer crates: symbolic alignment, then Tree Borrows.
MIRIFLAGS="-Zmiri-strict-provenance -Zmiri-symbolic-alignment-check" \
cargo +nightly miri test --locked -p my-parser
MIRIFLAGS="-Zmiri-tree-borrows -Zmiri-strict-provenance" \
cargo +nightly miri test --locked -p my-parser
# Concurrency: many schedules for the threaded tests.
MIRIFLAGS="-Zmiri-many-seeds=0..16" cargo +nightly miri test --locked <test_filter>
# Endianness: interpret on a big-endian target.
cargo +nightly miri test --locked -p my-parser --target s390x-unknown-linux-gnuFlag hazards:
- A misaligned read passes default Miri on some seeds and fails on others. Add
-Zmiri-symbolic-alignment-checkto make the failure certain. It gives false positives when code aligns a pointer with manual integer arithmetic;align_tois fine in both modes. -Zmiri-strict-provenancerejects every deliberate exposure, includingwith_exposed_provenance. Run exposing code in a separate job without the flag. Nocfgreports the flag, socfg_attr(miri, ignore)cannot select these tests. Keep them out of the gate with-- --skip <name>or by crate (--exclude).-Zmiri-permissive-provenanceonly silences the warning. Miri can miss bugs on exposed pointers either way.- Miri reports 1 CPU by default, so
cargo miri testruns one test at a time and does not detect a race between two tests on a shared resource (measured on nightly 2026-05-15). Add-Zmiri-num-cpus=4or-- --test-threads=4for that job.RUST_TEST_THREADSdoes not reach the program under isolation.cargo miri nextest runruns each test in its own process and never detects such a race.
Read references/miri-flags-and-ci.md when you need another flag, for
example -Zmiri-seed=N to reproduce one failing seed. It has the full
MIRIFLAGS table.
10. Read an ASan report
| ASan error | Likely Rust cause |
heap-buffer-overflow | unsafe slice or pointer access past the end of a buffer |
heap-use-after-free | Raw pointer kept across a Vec or String reallocation, or used after drop() |
stack-use-after-return | Raw pointer to a local that escaped its function |
double-free | Box::from_raw called twice on one pointer, or ownership passed to FFI and also dropped in Rust |
alloc-dealloc-mismatch | C++ new memory freed with free, or the reverse, across the FFI boundary |
Triage order:
- Read frame
#0. It names the access, not always the defect. - Read the allocation and free stacks that ASan prints below the access.
- Find the
unsafeblock on the path between them. That block owns the bug. - If the path is pure Rust, write a Miri test for it. Miri names aliasing and provenance defects more exactly than ASan.
11. On-device sanitizers: Android and iOS
On Android arm64, use HWASan or MTE. Xcode instruments only the code that Xcode compiles. A prebuilt Rust static library gets no ASan or TSan instrumentation from the scheme setting. Run a host sanitizer build for the Rust code itself.
Read references/platform-sanitizers.md when you build for a device. It has the
platform requirements, the HWASan build and wrap.sh, the MTE manifest modes
and stack-MTE rebuild, the tombstone check, and the Xcode commands.
12. CI integration
Read references/miri-flags-and-ci.md when you write the CI jobs. It has a
GitHub Actions example and the job layout rules.
Treat a Miri failure as a build failure. Miri reports UB, not style.
Related skills
rust-unsafe: unsafe patterns and the review checklist forunsafememory-model: atomics, memory ordering,loomrust-debugging: symbolication andaddr2linefor sanitizer and tombstone framesrust-test-tools: property tests and fuzzing that feed these toolsrust-jni,uniffi-boundary: FFI layers that Miri cannot executerust-android-build: Android target setup for the device builds
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

