mem::transmute called
| Vulnerability potential | High |
| DDoS potential | Low |
std::mem::transmute is one of the most unsafe operations in Rust
Impact
std::mem::transmute::<Src, Dst>(x) reinterprets the bytes of x as a value of
type Dst with no checks beyond size_of::<Src>() == size_of::<Dst>(). It
bypasses every type-system guarantee at once. If the source bit pattern is not a
valid Dst, the result is immediate undefined behavior: a transmuted bool,
char, enum, or reference holding an out-of-range or null/dangling value lets
the optimizer assume impossible things and miscompile surrounding code.
Concrete consequences include creating references to deallocated or arbitrary
memory (use-after-free, wild pointers), forging a &mut alias to data that is
also reachable as & (breaking the aliasing model and enabling data races),
fabricating invalid enum discriminants, and changing lifetimes to “extend”
borrows past the data they point to. Any of these can corrupt memory and crash
or be steered by an attacker.
Vulnerability potential
- Memory corruption / RCE. Transmuting integers to pointers/references, or one struct layout to an incompatible one, produces reads and writes through wild pointers. An attacker who influences the transmuted bytes can gain controlled out-of-bounds access, the foundation of arbitrary code execution.
- Lifetime laundering → use-after-free. Transmuting
&'a Tto&'static T(or otherwise lengthening a lifetime) defeats the borrow checker; the reference outlives its referent and is later read/written as dangling memory. - Aliasing violation → data races / torn state. Transmuting
&Tto&mut Tcreates an exclusive reference that aliases shared ones, violating “XOR mutability” and enabling unsynchronized concurrent mutation. - Invalid-value UB → info leak / corruption. Transmuting bytes into types
with validity invariants (
bool,char, references,NonNull, niche-using enums) yields values the compiler treats as impossible, producing unpredictable behavior that can leak or overwrite adjacent data.
Because it can directly produce memory corruption controllable by inputs, the vulnerability potential is High; it can also simply crash (Low DoS), but the defining risk is unsoundness.
Technical details
transmute is a compiler intrinsic: it copies the bytes of the argument and
hands them back typed as Dst. The only statically enforced rule is equal
size (checked at compile time; differently sized transmutes are a hard error).
Everything else — that the bytes form a valid Dst, that layout matches, that
lifetimes and aliasing are respected — is the programmer’s unchecked obligation.
Layout is not guaranteed
Rust’s default repr(Rust) layout is unspecified and may differ between types,
between compiler versions, and even between generic instantiations. Transmuting
between two structs that “look the same” is therefore unsound unless both are
repr(C) (or repr(transparent)) with identical, explicitly defined layouts.
Almost always avoidable
The standard library documents safe replacements for nearly every real use:
x as U for numeric casts, f32::to_bits/from_bits for float↔int,
ptr.cast() and as for pointer retyping, slice::from_raw_parts for building
slices, char::from_u32, and pointer::cast/NonNull helpers. Transmute should
be a last resort, confined to a tiny audited unsafe block with a safety
comment proving validity, layout, and lifetimes.
Catching the issue
Compiler and lint
rustc enforces the size-equality rule and emits the invalid_value lint for
some obviously-wrong transmutes (e.g. producing an uninhabited type). Clippy adds
a family of targeted lints: clippy::transmute_ptr_to_ref,
transmute_int_to_char, transmute_int_to_bool, transmute_ptr_to_ptr,
useless_transmute, transmute_undefined_repr, and
wrong_transmute — most suggesting the safe alternative.
Dynamic UB detection
Run the code under Miri (cargo +nightly miri test), which interprets the
program against Rust’s operational semantics and reports invalid values,
out-of-bounds, use-after-free, and aliasing (Stacked/Tree Borrows) violations
that a transmute introduces. Compile with -Z sanitizer=address/undefined
(ASan/UBSan) for native detection of the resulting bad accesses.
Review
Treat every transmute as a soundness review item: require an explicit
# Safety comment justifying size, validity, repr, and lifetimes, and prefer
#![deny(clippy::transmute_ptr_to_ref)] and friends in the project lint config.
How to reproduce
Run the following under Miri (cargo +nightly miri run) to see the undefined
behavior reported; transmuting an integer into a reference fabricates a wild
pointer that is then dereferenced.
fn main() {
unsafe {
// Reinterpret a small integer as a reference, then read through it.
let bogus: &i32 = std::mem::transmute(1usize); // invalid: not a valid &i32
println!("{}", *bogus); // UB: dereference of a wild pointer
}
}