Memory Safety
Memory Safety
Possible physical
states of the
computational device
Observable states of
the computational
device
Small C/C++
RegExp Java/Go Safe Rust Assembly
FSM Unsafe
Rust
What does memory safety provide?
● In C/C++, pointers and array indices are exchangeable
● They are also special in a particular way:
”Here be unicorns”
Why is memory corruption special?
● In C/C++, corrupting a pointer or array index risks letting the unicorns escape
and trample all over all program states.
● A corrupted pointer (or index) can alias to pretty much anything. Writing to it
breaks the relationship between source code and program states.
● This graph stays intact no matter how bizarre the values in the business logic
variables become.
● You can still reason about what is going on from the source
How is memory safety usually achieved?
Memory safety is commonly viewed as two components
- Spatial memory safety: You cannot access outside of array boundaries.
- Temporal memory safety: You cannot access memory after it has been released or
before it has been allocated.
In theory, you could prove for a given C/C++ program that it satisfies these properties. In
practice for most codebases, this isn’t done, so languages (or hardware) are modified to
have safety mechanisms.
These safety mechanisms can be implemented in runtime, during compile-time, or a
combination of both.
Application logic can still become arbitrarily confused. But the goal is to avoid such
a confusion to ever allow the dereference of a corrupt pointer.
Obtaining spatial safety
Spatial safety is usually (not always) obtained through the following steps:
- Array accesses go through a centralized chokepoint (e.g. a an Array class or somesuch).
- Run-time checks on the centralized chokepoint ensure that indices into the array are within
bounds.
- Remove pointer arithmetic. You can have a pointer to an object, even to one in an array, but
you cannot modify it.
- If you need pointer arithmetic, the assumption is that you have an underlying array of
identical objects. You can have a span or a slice that you can index into which references
the same underlying memory.
- Reduce the need on the language level to juggle indices by having “for each element in
container”-style loop structures.
- Until the emergence of Rust, “memory safe language” was (falsely) used as
synonymous with “garbage collected language”.
- But … TANSTAAFL
The most important wall of our time
The memory wall. Why linked lists suck. Cache rules everything around me.
Cost of garbage collection
- Hardware evolution has “moved against” GC.
- When GC was invented, memory access was ~1-5 cycles. Now it’s 100s of cycles.
- Global datacenter energy draw is ballpark ~$100bn annually (1.1 pWh * 0.083
per kWh). DRAM draws between 25%-30%, CPU around 60%.
- If I 5x or 3x available DRAM, I’ll get close to doubling power consumption.
- Power cost of GC-based memory safety in production may be anywhere
from a few billion to tens of billions annually (not counting Capex for hardware
purchase and software rewrites).
Quick note on language design and GC: Go vs. Java
- Java has a very mature and sophisticated GC infrastructure with decades of
production experience.
- Go has a very naive and simple GC.
- Why is in-production performance approximately the same?
- Go’s language design allows programmers to explicitly perform stack-based
allocations.
- Java doesn’t, leaving the work to move allocations to the stack to the
optimizer.
- Many more objects make it to the heap.
- Go allows arrays-of-piece-of-data, reducing heap fragmentation.
- Language design and idiomatic use matter.
Flavor 3: Reference
counting
and runtime array
checking
Reference counting
- Apple is religious about battery life (example: ECC to extend it)
- DRAM refresh is a huge component of total power draw of idle devices
- If you are religious about battery life, you need to be religious about driving
down DRAM usage.
- Design decision for Swift: Reference counting in place of garbage collection.
- Achieves temporal safety even without a garbage collector, and achieves the
upper bound of manual memory management performance (e.g. at least as
good as the worst manual memory manager).
Pro/Cons of reference counting
Pro: Compact heap.
- Keeps memory overhead low. You don’t pay power to keep your garbage alive in
DRAM.
Con:
- Enforced heap structure: Not all problems lend themselves easily to the ownership semantics.
- Directed cyclic graphs or RCU-like structures are impossible or near-impossible in safe Rust.
- Learning curve: Many developers dislike arguing with the borrow checker.
- Also relevant when CPU, Baseband, NIC, GPU don’t trust each other.
Surprising callbacks out of the type system
Classical C++ browser bug pattern:
1. Take a reference to an object.
2. Read a value from the object.
3. Perform a check on a value from the object.
4. Inadvertently cause a callback into Javascript. Javascript mutates the
underlying object / heap.
5. Return into C++ in an incoherent state, corrupt memory.
If you rely on your type system to provide memory safety, the invariants of the type
system need to be kept intact by any other language you call into.
This is conceptually a variant of TOCTOU shared-memory bugs, in some sense.
Issues around FFI (and GC and type systems)
Subtleties about FFIs and memory-safe languages can fill a book.
- The vast majority of issues were subtle bugs in the JIT engines.
- These often led to generation of incorrect / unsafe JIT’ed Javascript.
- JS is special: Most other languages trust the compiler input.
- JS needs to both be blazingly fast (JIT, sophisticated optimization) and treat
input as malicious
- Unique problems: Emit fast code fast from adversarial source.
Hardware errata
If the hardware misbehaves, all bets are obviously off.
- Rowhammer-style bugs
- AMD Bobcat CPU series JIT issues
- AMD SIMD Register Information Leakage
- “Cores that don’t count” - Mercurial cores
GPU and xPU interactions
- CPUs are increasingly a bottleneck.
- Systems are mutating to network-of-systems communicating via shared
memory
- CPU <-> GPU have shared DMA communication. CPU <-> NIC too, and more
heterogeneous compute is added.
- Driver logic for managing shared memory is prone to logic bugs:
- https://starlabs.sg/blog/2025/12-mali-cious-intent-exploiting-gpu-vulnerabilities-cve-2022-2270
6/
- New compute units need to respect operating system abstractions (process
isolation in GPU memory etc.)
What’s next?
The role of LLMs and AI in the process
The role of AI
- LLMs as drivers for theorem provers are improving rapidly. This opens a
number of new opportunities:
- Verification of unsafe Rust is currently done via Iris / Coq. Getting LLMs to drive a theorem
prover to validate more Rust crates.
- Retrofitting memory safety into C++ will almost certainly require more code annotations. LLMs
can help write some of these annotations.
- As LLMs-as-drivers-for-theorem-provers mature and improve, the cost of verifying code will
come down.
Research & Engineering ahead
Research & Engineering topics
- We know how to build safe applications now.
- Yet almost nothing beyond some network services in production are safe:
- We don’t have a memory-safe mainstream OS kernel.
- We don’t have memory-safe mobile phone basebands.
- We don’t have a memory-safe ffmpeg.
- We don’t have a broadly-deployed memory-safe browser engine (yet?).
- We don’t have a memory safe JS JIT engine, and don’t know how to get there.
- All kernel-bypass high-performance networking libs are unsafe (DPDK etc.)
- Outside of Oxide, few firms write firmware in Rust.
- Even Rust in the Linux Kernel is struggling (see recent discussions).
- Very few Rust crates can be verified to have written safe unsafe Rust.
- We have no viable path to convert legacy C/C++ code to memory safety.
- There’s a lot of work ahead.
Research & Engineering topics
- The language of the future will not be today’s Rust or C++
- Type systems will continue to evolve to provide more and better guarantees
- Theorem proving on top of more sophisticated type systems will become
more powerful
- Most likely, AI systems will learn much better on more stringently typed
languages, because they have a (partial) correctness oracle.
With memory
corruption,
anything is
possible Hitler was a
leftist