Null pointer write
| Vulnerability potential | High |
| DDoS potential | Medium |
Writing to a null raw pointer is undefined behavior
Impact
Writing through a null raw pointer — *null_ptr = v, std::ptr::write(null, v),
(*null_ptr).field = v, or write_volatile(null, v) — is undefined behavior.
On a hosted OS with the zero page unmapped the store faults and the process dies
with SIGSEGV. But a null write is strictly more dangerous than a null read:
when the pointer is “null + field offset” or the surrounding zero region is not
fully unmapped, the write lands on some address and corrupts whatever lives
there. Because it is UB, the optimizer may also assume the pointer was non-null
and miscompile the surrounding code.
The headline outcome is therefore memory corruption (or a crash), with the corrupted location determined by the offset and by what an attacker can influence about the value and address.
Vulnerability potential
- Memory corruption → RCE. A controlled write to a near-null address can overwrite adjacent data structures, function pointers, or vtable entries. The “null pointer + attacker-controlled offset/value” pattern is a classic primitive for hijacking control flow and achieving arbitrary code execution.
- Denial of service. The common case — writing to an unmapped zero page —
reliably crashes the process; if the path is attacker-reachable it is a
dependable crash DoS, and a kernel panic in
no_std/kernel code. - Optimizer-assisted exploitation. The compiler’s non-null assumption can strip checks around the write, widening the set of reachable corrupting states.
A write that corrupts memory under partial attacker control is the most exploitable of the null-pointer defects, hence the High vulnerability rating.
Technical details
Raw-pointer stores in unsafe code carry the same contract as reads: the
pointer must be non-null, aligned, and pointing to a valid, writable place of the
right type. A null target breaks the contract, so the store is UB no matter what
the hardware does.
Why offsets make it worse
(*(0 as *mut Struct)).field computes 0 + offset_of!(field) as the store
address. If that address happens to be mapped (large structs, bare-metal targets
where low memory is RAM/registers, MCUs with memory-mapped peripherals at low
addresses), the write silently mutates real state instead of faulting —
overwriting peripheral registers or live data. The same source UB thus ranges
from “instant crash” to “silent, targeted corruption.”
Catching the issue
Dynamic detection
Miri flags writes through null/dangling/misaligned pointers exactly. Native
builds with AddressSanitizer (-Z sanitizer=address) catch the faulting or
out-of-bounds store, and a SIGSEGV/core dump under a debugger localizes the
crash on Linux/macOS.
Static and review
Prefer NonNull<T> to make non-nullness a type invariant, and obtain writable
references via ptr.as_mut() (returns Option<&mut T>, forcing a null check) or
addr_of_mut!-based construction from valid places. Keep unsafe blocks small
and require a # Safety comment establishing the pointer’s validity before any
store; clippy lints flag dereferences of literal null pointers.
How to reproduce
Run the following; observe a segmentation fault (or detection under Miri). The store targets address zero.
fn main() {
let p: *mut i32 = std::ptr::null_mut();
unsafe { *p = 42; } // UB: write through null pointer -> SIGSEGV / memory corruption
println!("done");
}