Yen Wang

0x10Work / Vulnerability research

Rust memory-safety bugs, reproduced and measured

Reproducing CVE-2021-25900 in smallvec under Miri, and measuring when an unsafe Rust bug is visible at all.

When August 2026 Role Independent lab Tools Rust nightly, Miri, Clippy
CVE-2021-25900
smallvec 1.6.0, reproduced under Miri
exit 0
unsafe out-of-bounds read in a release build
0
Clippy findings on that same bug
12
Rust bug classes mapped to a detector and a CWE

Reproducing a known advisory

RUSTSEC-2021-0003 (CVE-2021-25900) is a heap bug in smallvec 1.6.0. insert_many trusts the iterator's size_hint. Give it an iterator that lies, and it writes past the end of the buffer.

I wrote the lying iterator, called v.insert_many(0, lying), and ran it under Miri on nightly 1.99.0 (with rust-src for the Miri sysroot). Miri stopped it as a Stacked Borrows violation:

error: Undefined Behavior: attempting a write access using <572> at alloc166[0x5],
but that tag does not exist in the borrow stack for this location
  --> smallvec-1.6.0/src/lib.rs:1048:21
      ptr::copy(cur, cur.add(1), old_len - index)

A checker that has to fail

A reproduction only counts if the claim and the evidence agree. I built a small gate that compares a written claim against the Miri log. The correct smallvec claim returns MATCH and passes.

Then I gave it a trap: a claim that Miri had caught the data race in the time crate (RUSTSEC-2020-0071). Miri runs single-threaded here, so it cannot see that race. The gate returned MISS and failed, which is the right answer. A checker that only ever passes proves nothing.

When is the bug visible?

Five small programs, each run five times on rustc 1.95.0, scored by exit code:

CaseResult
Safe indexing out of boundspanic, exit 101
unsafe get_unchecked out of bounds, debug assertions onSIGABRT, exit 134
Same code, release buildexit 0. Undefined behaviour, no signal at all
u8 overflow, checked buildpanic, exit 101
u8 overflow, release buildexit 0, wraps silently to 44
Clippy on the unsafe case0 findings

Two things came out of this. First, an exit code cannot tell real undefined behaviour from correct handling, because both exit 0. That is exactly the gap Miri closes. Second, whether an unsafe bug is detected at all depends on the build profile and the standard library version, because the only detector in the debug case is the standard library's own precondition check.

I corrected myself twice here. "Unsafe means a silent exit 0" was only half right. And I had called this "containment", when what I was measuring is detection.

A bug-class map

To keep findings precise, I wrote a 12-row map from Rust bug class to the tool that catches it and the root-cause CWE: heap overflow (CWE-122), out-of-bounds write (CWE-787), use after free (CWE-416), double free (CWE-415), integer overflow (CWE-190), type confusion (CWE-843), uninitialised memory (CWE-457), data races (CWE-362), plus FFI-boundary and panic-safety bugs. The rule I hold myself to is to cite the root cause, not the consequence.