flawopen.com/Memory Safety/Rust unsafe
Is Rust's unsafe block still memory safe?
unsafe does not switch off Rust’s checks. It permits specific operations whose safety conditions the compiler cannot prove, and the programmer must discharge those obligations. The key consequence is that an unsound unsafe abstraction can let ordinary safe callers trigger undefined behaviour (UB), even though they never write unsafe.
Key terms
- Unsafe operation:
- An operation with safety conditions the compiler cannot prove, such as dereferencing a raw pointer.
- Sound abstraction:
- A safe API whose safe callers cannot trigger undefined behaviour.
- size_hint():
- An iterator size estimate for optimization; it is not an exact count or memory-safety proof.
- Undefined behaviour (UB):
- Program behaviour Rust does not define; unsafe code must prevent safe callers from triggering it.
How an unsound safe abstraction fails
A safe API accepts an iterator
A caller passes an ordinary iterator to a function that exposes no unsafe precondition.
The implementation reserves from a hint
The function reserves only the reported lower bound, which may be smaller than the number of items the iterator will yield.
Unchecked writes exceed spare capacity
The implementation writes every yielded item through a raw pointer without growing the allocation or checking capacity for each item.
Safe use reaches undefined behaviour
If the iterator yields more items than fit, a safe call can cause an out-of-bounds write. Use Vec::extend or another implementation that grows safely.
// UNSOUND: reserve uses only the lower hint, // then unchecked writes consume every item. fn append_unsound(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { let (lower, _) = iter.size_hint(); dst.reserve(lower); let start = dst.len(); let mut written = 0; unsafe { let out = dst.as_mut_ptr().add(start); for item in iter { // UB once writes exceed spare capacity. out.add(written).write(item); written += 1; } dst.set_len(start + written); } }
// SOUND: Vec::extend handles capacity // growth and length updates safely. fn append(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { dst.extend(iter); }
What Rust's guarantee actually says
The precise guarantee is: safe Rust cannot cause undefined behaviour, provided the unsafe code it relies on is sound. The Rust Reference defines soundness at the boundary: unsafe code must ensure that safe clients cannot trigger undefined behaviour. The compiler does not verify every proof obligation inside an unsafe operation; programmers and reviewers must discharge them.
This is still a major practical advantage over C or C++: Rust makes memory-sensitive operations and their safety obligations more visible, so reviewers can focus on unsafe boundaries and safe abstractions built on them. Those boundaries include unsafe blocks, unsafe function contracts, unsafe trait implementations, FFI declarations, and unsafe attributes. They are not always confined to one small block. A concrete case is CVE-2021-25900 in smallvec, where SmallVec::insert_many reserved from an iterator’s lower size hint and wrote more elements than the allocation held; it was rated CVSS 9.8.
Soundness is the standard, not carefulness
An API is unsound if safe code can use it to trigger undefined behaviour. For example, Iterator::size_hint provides bounds useful for optimization, not an exact item count: a valid iterator can yield more than the lower bound, and a buggy iterator may violate its estimate. The standard documentation explicitly says the hint must not be used to omit bounds checks in unsafe code. Incorrect safe trait behaviour may also cause logic errors or panics; those are not automatically undefined behaviour, but unsafe code must preserve memory invariants even when caller-provided code behaves unexpectedly.
Unsafe code is therefore non-local: a memory invariant established in one place may be relied on by safe APIs above it. If the invariant is wrong, the defect is in the abstraction that exposed the safe API, not in safe callers that use it according to its contract.
Practical rules
- ✓Do not treat safe trait methods as memory-safety proofs. In particular, use
size_hint()only to optimize; preserve checks or grow capacity as needed. - ✓Preserve invariants across panics. Caller-provided code such as
Iterator::next, a destructor, or a closure can unwind. Ensure partially initialized state remains valid and correctly dropped. - ✓Write a
// SAFETY:comment stating what must hold and why it does. Unexplained unsafe is unreviewable unsafe. - ✓Keep blocks narrow. Wrapping a whole loop to avoid repeating the keyword expands what a reviewer must reason about.
- ✓Run Miri on exercised tests. It can detect some undefined behaviour on executed paths, including out-of-bounds access, use-after-free, and invalid aliasing; it is not a proof that all unsafe code is sound.
- ✓Audit dependencies.
cargo auditchecks known RustSec advisories;cargo geigercan help inventory unsafe usage. Neither replaces reviewing the unsafe contracts of code you ship.
FAQ
#![forbid(unsafe_code)] can prevent unsafe syntax in the crate where it is enabled, which is useful for application crates. It does not govern dependencies, and unsafe operations remain necessary in some implementations, FFI boundaries, and libraries.
Yes. An unsafe trait documents obligations that implementations must uphold; unsafe code can rely on those obligations. TrustedLen is an unstable standard-library trait for iterators that promise an exact size hint. Ordinary Iterator::size_hint does not provide that guarantee.
No. Rust’s safety model narrows where memory-safety obligations arise and makes them explicit at unsafe boundaries. It does not eliminate the need to audit unsafe code, dependencies, FFI contracts, or safe APIs built on unsafe implementations.
References
- The Rust Reference — unsafe code and soundness
- Rust Reference — the unsafe keyword and proof obligations
- Rust standard library — Iterator::size_hint contract
- Rustonomicon — working with unsafe
- RustSec Advisory RUSTSEC-2021-0003 — smallvec buffer overflow
- GitHub Security Advisory GHSA-43w2-9j62-hq99 — CVE-2021-25900
- The Rustonomicon — the guide to unsafe Rust
- RustSec Advisory Database
- Miri — an interpreter that detects undefined behaviour
- CWE-787 — Out-of-bounds Write
- Case study: the smallvec overflow (CVE-2021-25900)