Rust - Memory Safety Under the Hood

The primary reason Rust was created and is actively replacing C and C++ in systems infrastructure is memory safety without garbage collection.

In traditional systems languages (C and C++), memory management is manual. Decades of engineering experience have proven that even the most disciplined engineering teams cannot prevent memory corruption vulnerabilities at scale.

The 70% Memory Safety Problem

Independent studies published by major technology vendors demonstrate that memory safety bugs account for roughly 70% of all high-severity security vulnerabilities:

  • Microsoft Security Response Center (MSRC): ~70% of all CVEs patched across Windows and Microsoft products each year are memory safety bugs.
  • Google Chromium Project: ~70% of all critical security bugs in Chromium/Chrome are memory safety issues.
  • Android Open Source Project (AOSP): Historically, over 75% of severe Android vulnerabilities originated in native C/C++ memory corruption.

Categories of Memory Vulnerabilities

These bugs fall into two main categories:

Category Vulnerability Type Mechanism in C/C++ Rust Compile-Time Prevention
Temporal Safety Use-After-Free (UAF) Accessing a heap pointer after free() or delete. Single ownership + compile-time lifetime analysis (Drop).
Temporal Safety Double-Free Calling free() twice on the same memory block. Ownership moves; moved-from values cannot be dropped twice.
Temporal Safety Dangling Pointers Pointer to a local stack variable that unwound. Lifetime parameters ('a) verify references cannot outlive pointees.
Spatial Safety Buffer Overflow / Out-of-Bounds Reading or writing past array/buffer boundaries. Mandatory boundary checks by default; slices track pointer + length.
Spatial Safety Format String Exploits Unchecked user input passed directly to printf(). Format macros validate argument counts and types at compile time.
Concurrency Data Races Two threads accessing memory concurrently with at least one write. Send and Sync auto-traits; Aliasing XOR Mutability theorem.

The Mathematical Foundation: Aliasing XOR Mutability

The central insight behind Rust's memory safety is the Aliasing XOR Mutability invariant (often abbreviated as &T XOR &mut T):

At any point in time, you may have either:

  1. Any number of immutable (shared) references (&T) to a resource, OR
  2. Exactly one mutable (exclusive) reference (&mut T) to a resource, but NEVER both simultaneously.
       +---------------------------------------------+
       |           Can Multiple References Exist?    |
       +---------------------------------------------+
                              |
              +---------------+---------------+
              |                               |
             Yes                              No
              |                               |
              v                               v
    +-------------------+           +-------------------+
    |  Shared (&T)      |           | Exclusive (&mut T)|
    |  - Read-Only      |           | - Read + Write    |
    |  - Safe to Alias  |           | - Cannot Alias    |
    +-------------------+           +-------------------+

Why Aliasing + Mutation Causes Corruption

In C and C++, pointer aliasing combined with mutation leads to subtle, catastrophic memory corruption:

  1. Iterator Invalidation: Modifying a collection (e.g. vector.push_back()) while an iterator points inside it can trigger a reallocation, leaving the iterator pointing to freed memory (Use-After-Free).
  2. Reentrancy Bugs: A function modifying a buffer while another function reads from an alias of that same buffer.
  3. Data Races: Two threads accessing the same memory location simultaneously where one writes without synchronization.

By enforcing Aliasing XOR Mutability at compile time, Rust mathematically eliminates these classes of bugs without needing runtime checks or locks.

The Borrow Checker Under the Hood

The Borrow Checker is the static analysis engine inside rustc that enforces ownership and borrowing invariants.

From Lexical Scopes to Mid-Level IR (MIR)

Early Rust (pre-2018) checked borrows using rigid lexical scopes ({ ... }). Modern Rust operates on the Mid-Level Intermediate Representation (MIR) control-flow graph:

[Statement: let mut x = 5]
           |
           v
[Borrow: let r = &mut x] ---> Live Region of 'r' begins
           |
[Read from r]
           |
[Last use of r] -------------> Live Region of 'r' ends (Non-Lexical Lifetime)
           |
[Direct read/write on x] ----> Allowed! (No active borrow remains)
  1. Region Inference: The compiler computes the set of MIR execution points where a reference is actually used.
  2. Non-Lexical Lifetimes (NLL): Lifetimes end at the last point of use rather than at the closing brace of a block, making idiomatic code pass the borrow checker naturally.
  3. Conflict Detection: If a mutable borrow overlaps with any other borrow of the same memory path in the control-flow graph, rustc rejects compilation with an explanatory diagnostic.

The Affine Type System & Zero-Cost Destruction

Rust employs an Affine Type System: every value can be used at most once.

Moves vs Copies

  • Copy Types: Primitive integers, floats, booleans, and fixed arrays of Copy types implement Copy. Assigning or passing them performs a bitwise copy on the stack.
  • Non-Copy Types: Heap-allocated structures (Vec, String, Box<T>) use Move Semantics.

Under the hood, a move in Rust is simply a shallow memcpy of the pointer/descriptor on the stack. Crucially, the compiler marks the source variable as uninitialized:

let v1 = vec![1, 2, 3];
let v2 = v1; // v1 moved to v2

// Compiler ERROR at build time:
// println!("{:?}", v1); // borrow of moved value: `v1`

Because v1 is statically invalidated:

  • There is no double-free when the scope ends.
  • Only v2's destructor (Drop::drop) executes.
  • No reference counting, heap tracking, or garbage collector is required. Destruction is deterministic (RAII) and costs zero runtime cycles.

Eliminating the "Billion-Dollar Mistake": Absence of Null

Tony Hoare famously termed his 1965 invention of the null reference a "billion-dollar mistake" due to countless vulnerabilities and application crashes.

In safe Rust, null pointers do not exist. Optional values are explicitly represented via the Option<T> enumeration:

enum Option<T> {
    Some(T),
    None,
}

The compiler forces the developer to handle both cases via pattern matching or monadic methods (unwrap_or, map, and_then).

The Null Pointer Optimization (NPO)

A common concern in systems programming is memory overhead: does Option<T> require an extra byte for an enum tag?

Through the Null Pointer Optimization (NPO), Rust guarantees that for types that cannot have a null value (such as references &T, &mut T, non-null pointers NonNull<T>, and Box<T>), the compiler uses the memory address 0x0 to represent None:

use std::mem::size_of;

assert_eq!(size_of::<&str>(), size_of::<usize>()); // 16 bytes (fat pointer: ptr + len)
assert_eq!(size_of::<Option<&str>>(), size_of::<usize>()); // Still exactly 16 bytes!

This delivers complete compile-time null safety with zero memory overhead and zero runtime performance penalty.

The unsafe Boundary & Trust Invariants

Rust does not pretend that hardware is inherently memory-safe. Interacting with memory-mapped registers, issuing kernel system calls, calling C libraries via FFI, and building high-performance primitives (like Vec or Arc) require direct memory access.

Rust handles this with the unsafe keyword.

What unsafe Actually Does

Marking a block or function as unsafe does NOT disable the borrow checker, turn off type checks, or bypass lifetimes. It only unlocks five specific capabilities:

  1. Dereferencing raw pointers (*const T, *mut T).
  2. Calling unsafe functions or Foreign Function Interface (FFI) bindings.
  3. Implementing unsafe traits (such as Send or Sync).
  4. Mutating mutable static variables (static mut).
  5. Accessing fields of a union.
// Safe code encapsulates unsafe operations:
pub struct MyBuffer {
    ptr: *mut u8,
    len: usize,
}

impl MyBuffer {
    pub fn read(&self, index: usize) -> Option<u8> {
        if index < self.len {
            // Safety invariant verified: index is guaranteed within bounds
            unsafe { Some(*self.ptr.add(index)) }
        } else {
            None
        }
    }
}

Encapsulation: The Soundness Invariant

The fundamental philosophy of Rust is soundness encapsulation:

  • unsafe code must maintain the invariants expected by safe Rust.
  • If safe Rust crashes or corrupts memory, the bug is guaranteed to reside in an unsafe block or compiler bug, dramatically narrowing the audit surface for security reviews.

Why Rust is Replacing C and C++

Government bodies and major technology firms have recognized that developer discipline alone cannot solve memory safety in C/C++:

Government Directives & Standards

  • US White House Office of the National Cyber Director (ONCD): Published "Back to the Building Blocks", urging technology organizations to adopt memory-safe programming languages (specifically naming Rust) for critical infrastructure.
  • CISA, NSA, and FBI: Joint cybersecurity advisories explicitly recommend transitioning legacy systems from C/C++ to memory-safe languages.
  • European Union ENISA: Recommendations prioritizing memory-safe system designs in European critical software infrastructure.

Industry Migrations

1. Linux Kernel (Rust for Linux)

Starting in Linux 6.1, Rust was merged into the kernel tree as the second official programming language. Kernel drivers and hardware abstractions written in Rust guarantee the absence of kernel panic-inducing Use-After-Free and data race bugs.

2. Android OS

Google introduced Rust into the Android platform for all new native code:

  • Key native services—Bluetooth (Gabeldorsche), Android Keystore, Ultra-Wideband (UWB), and DNS-over-HTTP/3—were rewritten in Rust.
  • Empirical result: Memory safety vulnerabilities in Android fell from 76% in 2019 to under 24% by 2023.
  • To date, zero memory safety vulnerabilities have been discovered in Android's production Rust code.

3. Windows Kernel

Microsoft rewritten critical components of the Windows kernel—including win32kbase_rs (the GDI/font rendering subsystem)—in Rust, systematically eliminating legacy vulnerabilities that previously allowed kernel privilege escalation.

4. High-Performance Infrastructure

  • Cloudflare Pingora: Rewrote its core edge proxy from Nginx (C) to Rust, processing trillions of daily requests with a 70% drop in CPU usage and an elimination of crash-inducing memory bugs.
  • AWS Firecracker: Developed in Rust to provide microVM sandboxes for serverless AWS Lambda and Fargate environments, achieving millisecond cold starts with strong security isolation.
Recommended Reading
Sponsored
Programming Rust
Systems Rust
Programming Rust
By Jim Blandy, Jason Orendorff, Leonora TindallView on Amazon