Skip to content

04 — Rust in the Linux Kernel

Technical Overview

The Linux kernel merged Rust support in version 6.1 (December 2022) — a historic decision for both Linux and the Rust language. Rust becomes the second language (after C and assembly) officially supported for kernel development. The motivation is direct: approximately 65-70% of security vulnerabilities in the Linux kernel are memory safety bugs, and C cannot prevent them. Rust's ownership model eliminates the entire class of memory safety bugs that drives the majority of kernel CVEs. This document covers the history of Rust's kernel integration, the technical architecture of Rust kernel code, the current state of Rust drivers, and the significant challenges that remain.

Prerequisites

  • Linux kernel architecture: modules, drivers, VFS, networking stack
  • Rust ownership model and unsafe Rust (see 02, 03)
  • Kernel memory allocators: slab/slub, GFP flags
  • Linux driver model: platform drivers, probe/remove lifecycle
  • Kernel locking primitives: spinlock, mutex, RCU
  • Kernel error handling: errno, IS_ERR(), ERR_PTR()
  • Understanding of no_std in embedded Rust

Historical Context

The idea of Rust in the Linux kernel was discussed as early as 2017. The first serious proposal came from Google and the Rust for Linux project (started by Miguel Ojeda) in 2020. Linus Torvalds, historically skeptical of adding new languages to the kernel, engaged with the proposal at Linux Plumbers Conference 2020. His position evolved from "I have no problem accepting Rust code in the kernel, as long as it's written in a way that makes sense" (2020) to actively supporting the merge in 2022.

The turning point was Google's public statement that approximately 68% of Android CVEs were memory safety bugs, predominantly in C code in drivers — the exact category that Rust targets. The Rust for Linux project received FOSS funding from Google and Microsoft to support full-time development.


Why Rust in the Kernel: The CVE Motivation

The Linux kernel is written in C, the language most prone to memory safety bugs. The kernel's security posture depends on: 1. Code review (imperfect — humans make mistakes) 2. Runtime checks (KASAN, KMSAN — too expensive for production) 3. Exploitation mitigations (KASLR, CFI, SMEP — raises bar, doesn't eliminate)

Statistics (from Android Security team, Google, and various analyses): - 65-70% of Linux kernel CVEs are memory safety bugs — primarily in device drivers - Drivers are the majority of kernel code by line count (~60%) - Drivers are written by many contributors with varying expertise in kernel memory management - A single driver bug can give an attacker kernel privileges (ring 0)

The Rust strategy: - Do not rewrite existing C drivers in Rust - Write new drivers in Rust, eliminating new CVEs from new code - Over time, the fraction of code with memory safety guarantees grows - Critical drivers may be rewritten in Rust when the benefits justify the cost


Technical Architecture: Rust Kernel Abstractions

No Standard Library

Rust kernel code cannot use the Rust standard library (std). The standard library depends on OS services (heap allocation via OS allocator, threads via OS threads, I/O via OS file descriptors). In the kernel, there is no OS to provide these services — the kernel IS the OS.

Rust in the kernel uses #![no_std] — a mode where only the language core (core) crate is available:

// All kernel Rust modules begin with:
#![no_std]

// The `core` crate provides:
//   - Primitive types (i32, bool, etc.)
//   - Traits (Iterator, From, Into, etc.)
//   - Option<T>, Result<T,E>
//   - core::fmt (formatting), core::mem (mem::size_of, etc.)
//   - core::ptr (raw pointer operations)

// NOT available from std:
//   - Vec<T> (heap allocation requires kernel allocator)
//   - String (similarly)
//   - std::thread (kernel has different threading model)
//   - std::sync::Mutex (kernel has its own mutex)
//   - std::io (kernel has its own I/O)

The Kernel Allocator

The alloc crate provides heap-allocated data structures (Vec<T>, Box<T>, Arc<T>) that work with a custom allocator. The kernel crate provides a KernelAllocator that implements GlobalAlloc using kmalloc/kfree:

// kernel/rust/kernel/allocator.rs (simplified)
use core::alloc::{GlobalAlloc, Layout};

struct KernelAllocator;

unsafe impl GlobalAlloc for KernelAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // kmalloc with GFP_KERNEL flags
        bindings::kmalloc(layout.size(), bindings::GFP_KERNEL) as *mut u8
    }

    unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
        bindings::kfree(ptr as *mut core::ffi::c_void);
    }
}

#[global_allocator]
static ALLOCATOR: KernelAllocator = KernelAllocator;

But kernel allocation can fail (OOM). Unlike user-space Rust where allocation failure panics, kernel code must handle allocation failure gracefully. The Rust for Linux project uses a custom KVec<T> and KBox<T> that return Result<T, AllocError> on allocation failure.

Safe Wrappers Around C Kernel APIs

The core of Rust for Linux is a set of safe Rust wrappers around the kernel's C APIs. These wrappers: 1. Expose safe Rust types instead of raw pointers 2. Map kernel idioms (error pointers, reference counting) to Rust idioms (Result, Arc) 3. Use Rust's ownership model to ensure kernel resources are correctly managed

Example: wrapping a kernel mutex

// kernel/rust/kernel/sync/mutex.rs (simplified)

// The kernel's C mutex:
// struct mutex { ... };
// void mutex_lock(struct mutex *lock);
// void mutex_unlock(struct mutex *lock);

/// A kernel mutex that can protect data T
pub struct Mutex<T: ?Sized> {
    // Safety: The mutex is pinned (cannot be moved while locked)
    inner: UnsafeCell<T>,
    lock: bindings::mutex,  // the underlying C mutex
}

unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}

impl<T> Mutex<T> {
    pub fn lock(&self) -> MutexGuard<'_, T> {
        unsafe { bindings::mutex_lock(&self.lock as *const _ as *mut _) }
        MutexGuard { mutex: self }
    }
}

/// RAII guard: unlocks the mutex when dropped
pub struct MutexGuard<'a, T: ?Sized + 'a> {
    mutex: &'a Mutex<T>,
}

impl<'a, T: ?Sized> Drop for MutexGuard<'a, T> {
    fn drop(&mut self) {
        unsafe {
            bindings::mutex_unlock(&self.mutex.lock as *const _ as *mut _);
        }
        // mutex_unlock called exactly once, when guard drops
    }
}

impl<T: ?Sized> Deref for MutexGuard<'_, T> {
    type Target = T;
    fn deref(&self) -> &T {
        // Safety: we hold the mutex lock, so we have exclusive access
        unsafe { &*self.mutex.inner.get() }
    }
}

This wrapper ensures: the mutex is always unlocked (via RAII drop), no data races (the Deref requires the guard to be held), and the mutex cannot be used after the protected data is freed.

Example: wrapping kernel reference counting

The kernel uses kref (kernel reference counter) for reference-counted objects, similar to Rust's Arc<T>. The Rust wrapper:

pub struct Arc<T: AlwaysRefCounted> {
    ptr: NonNull<T>,
    _p: PhantomData<T>,
}

unsafe impl<T: AlwaysRefCounted + Send + Sync> Send for Arc<T> {}
unsafe impl<T: AlwaysRefCounted + Send + Sync> Sync for Arc<T> {}

impl<T: AlwaysRefCounted> Clone for Arc<T> {
    fn clone(&self) -> Self {
        // Safety: the object is alive (we hold a reference)
        unsafe { T::inc_ref(self.ptr.as_ref()) };
        Arc { ptr: self.ptr, _p: PhantomData }
    }
}

impl<T: AlwaysRefCounted> Drop for Arc<T> {
    fn drop(&mut self) {
        // Safety: we hold a reference, releasing it
        unsafe { T::dec_ref(self.ptr) };
    }
}

Error Handling: Result<T, Error>

The kernel uses negative errno values for errors, returned via int return values or via ERR_PTR() encoded in pointers. Rust wraps this in Result<T, kernel::error::Error>:

// kernel/rust/kernel/error.rs
pub struct Error(core::ffi::c_int);

impl Error {
    pub const fn from_errno(errno: core::ffi::c_int) -> Error {
        Error(errno)
    }
    pub fn to_errno(self) -> core::ffi::c_int {
        self.0
    }
}

// Convenient alias
pub type Result<T, E = Error> = core::result::Result<T, E>;

// Macro for converting kernel error codes to Rust Result
macro_rules! to_result {
    ($retval:expr) => {{
        let val = $retval;
        if val < 0 { Err(Error::from_errno(val)) } else { Ok(val) }
    }};
}

Kernel Rust code uses Rust's ? operator for error propagation, which is far cleaner than C's manual if (ret < 0) return ret; chains:

fn probe(pdev: &platform::Device) -> Result {
    let resource = pdev.get_resource(IoResource::Mem, 0)?;  // ? propagates error
    let iomem = IoMem::<DEVICE_SIZE>::try_new(resource)?;
    let irq = pdev.irq_resource(0)?;
    // If any of these fail, error is returned from probe
    Ok(())
}

Current State of Rust in the Kernel (as of 2024)

Merged in Linux 6.1 (December 2022)

The initial Rust merge included: - Build system integration (Kbuild support for Rust files) - rust/ directory for kernel abstractions (kernel crate, macros crate) - Basic abstractions: allocators, error types, synchronization primitives - Documentation and CI infrastructure - Sample Rust module in samples/rust/

Active Drivers and Subsystems (2023-2024)

PHY Driver (drivers/net/phy/ax88796b_rust.rs): The first Rust network PHY driver was merged in Linux 6.8 (March 2024). It uses the Rust phy driver framework to implement an Ethernet PHY driver for the ASIX AX88796B chip. The driver is a clean translation of the existing C driver, demonstrating that the Rust abstractions work for real hardware.

Android Binder (WIP): Google is working on a Rust reimplementation of the Android Binder IPC driver — one of the most security-critical kernel components (Binder vulnerabilities have enabled many Android privilege escalation exploits). The Rust Binder retains the existing C Binder's interface but reimplements the internals with ownership-based safety.

NVMe Driver (experimental): A Rust NVMe (Non-Volatile Memory Express) driver is being developed. NVMe is a high-performance SSD interface. The Rust driver demonstrates Rust's viability for high-performance storage drivers.

File System Infrastructure: Work is ongoing to provide Rust bindings for the VFS (Virtual File System) layer, enabling Rust filesystem drivers.

Key Infrastructure Abstractions Available

kernel::sync::{Mutex, SpinLock, RwLock, Arc, UniqueArc}
kernel::error::{Error, Result}
kernel::alloc::{KVec, KBox}
kernel::device::Device
kernel::platform::{Driver, Device} (platform driver framework)
kernel::net::phy::{Driver, Device} (network PHY framework)
kernel::fs::{FileSystem, Inode} (in development)
kernel::mm::{PhysAddr, VirtAddr, Pfn}

Kernel Rust Restrictions

1. No Unwinding

Rust's panics (from unwrap(), bounds check failures, arithmetic overflow in debug mode) can either abort the process or unwind the stack. Kernel code cannot unwind (stack unwinding requires a full unwind runtime and metadata that the kernel does not provide). Therefore, kernel Rust code compiles with panic = "abort".

This means any panic!() in kernel code causes a kernel panic (oops or BUG()). Kernel Rust code must handle all error cases via Result<T, E> — using unwrap() is forbidden in production kernel code.

2. No Stack Unbounded Recursion

The kernel stack is small (8-16KB on most architectures). Deeply recursive Rust code will overflow the kernel stack and cause a panic. All kernel Rust code must use explicit loops or bounded recursion.

3. No Floating Point (in most paths)

Using floating point in kernel code requires saving/restoring floating-point registers (kernel/FPU state management). Unless specifically enabled (kernel_fpu_begin()/kernel_fpu_end()), floating-point operations in kernel context are forbidden.

4. Custom Allocators

Standard Rust allocation failure panics. Kernel code must use fallible allocation APIs. The alloc crate's Vec::try_reserve() and similar fallible APIs are required.

5. No Standard Library Multithreading Primitives

std::thread is not available. Kernel threads use kthread_create(). The Rust kernel team provides kernel::task::Task abstractions.


Rust/C Interop Layer Diagram

┌──────────────────────────────────────────────────────────┐
│         Rust Driver (e.g., ax88796b_rust.rs)             │
│                                                          │
│  pub struct PhyDriver;                                   │
│  impl phy::Driver for PhyDriver {                        │
│      fn soft_reset(dev: &mut phy::Device) -> Result { }  │
│      fn config_init(dev: &mut phy::Device) -> Result { } │
│  }                                                       │
│                                                          │
│  (Safe Rust — no unsafe in driver code itself)           │
└──────────────────┬───────────────────────────────────────┘
                   │ calls into (via safe abstraction layer)
┌──────────────────▼───────────────────────────────────────┐
│  Rust Kernel Abstraction Layer (kernel/rust/kernel/)     │
│                                                          │
│  pub struct phy::Device { inner: *mut bindings::phy_device }
│  impl phy::Device {                                      │
│      pub fn read(&self, reg: u16) -> Result<u16> {       │
│          unsafe {                                        │
│              let ret = bindings::phy_read(               │
│                  self.inner, reg as i32);                │
│              to_result!(ret).map(|v| v as u16)           │
│          }                                               │
│      }                                                   │
│  }                                                       │
│                                                          │
│  (Thin unsafe layer: wraps C API, provides safe types)   │
└──────────────────┬───────────────────────────────────────┘
                   │ calls into (unsafe C bindings via bindgen)
┌──────────────────▼───────────────────────────────────────┐
│  Kernel C API (auto-generated bindings)                  │
│  include/linux/phy.h → bindings/bindings.rs              │
│                                                          │
│  extern "C" {                                            │
│      pub fn phy_read(phydev: *mut phy_device, regnum: c_int) -> c_int;
│      pub fn phy_write(phydev: *mut phy_device, ...) -> c_int;
│  }                                                       │
└──────────────────┬───────────────────────────────────────┘
                   │ C function implementations
┌──────────────────▼───────────────────────────────────────┐
│  Linux Kernel C Code (drivers/net/phy/phy-core.c, etc.)  │
└──────────────────────────────────────────────────────────┘

Key: The safety barrier is between the abstraction layer and the C bindings.
     Driver code above the barrier can be entirely safe Rust.
     Only the abstraction layer authors need to reason about unsafe.

Challenges and Criticisms

1. Kernel API Bindings Maintenance

The Rust kernel bindings are generated from kernel C headers using bindgen. Every time a kernel C API changes (function signature change, struct layout change), the Rust bindings must be updated. This creates a maintenance burden that the kernel community has been discussing.

The solution being developed: abstraction layer stability. Rather than binding directly to internal kernel APIs (which change frequently), the Rust abstraction layer exposes stable Rust APIs, and only the binding layer needs to update when C APIs change.

2. Unsafe Surface Area in the Abstraction Layer

The kernel Rust abstraction layer contains a significant amount of unsafe code — more than typical userspace Rust code — because it wraps inherently unsafe C APIs. The correctness of the entire Rust kernel codebase depends on this abstraction layer being sound.

Auditing this layer is essential and ongoing. The Rust for Linux project maintains a safety review process for all unsafe code in the abstraction layer.

3. Developer Adoption

Writing Rust kernel code requires learning both Rust (ownership model, lifetimes, async) and kernel development. This is a higher skill bar than C-only kernel development. The community concern is that the Rust kernel contributor pool is smaller than the C contributor pool, potentially slowing adoption of Rust for new drivers.

The counter-argument: Rust's explicit safety documentation and borrow checker errors often catch bugs that would reach code review in C, reducing review burden.

4. Build System Complexity

Rust requires a modern toolchain (recent rustc, Cargo, and a specific nightly for kernel features). This adds to the kernel's build dependencies. The kernel uses a specific pinned Rust toolchain version to ensure reproducible builds.

5. Performance Concerns

Early experiments show Rust kernel code has performance comparable to C kernel code (as expected from Rust's zero-cost abstractions). However, some patterns in Rust (e.g., Arc<T> reference counting vs C's kref) have different performance profiles that require careful use.


The Rust-for-Linux Project

Maintainers: - Miguel Ojeda (primary maintainer, full-time, Google-funded) - Wedson Almeida Filho (early contributor, Microsoft, now on Android team at Google) - Alice Ryhl (Tokio team member, full-time Rust for Linux) - Gary Guo (compiler/language support)

Repository: github.com/Rust-for-Linux/linux (development tree)

Upstream: Rust support merged into Torvalds' tree as of Linux 6.1 (December 2022).

Mailing list: lore.kernel.org/rust-for-linux


Debugging Notes

# Build a kernel with Rust support
make LLVM=1 rustavailable  # verify Rust toolchain is set up
make LLVM=1 menuconfig      # enable CONFIG_RUST=y
make LLVM=1                 # build

# Check Rust in your kernel
zcat /proc/config.gz | grep CONFIG_RUST

# View kernel Rust documentation
make LLVM=1 rustdoc  # generates HTML docs for kernel Rust APIs

# Test Rust kernel code
make LLVM=1 rusttest  # runs Rust unit tests in the kernel codebase

# Rust kernel coding style
make LLVM=1 rust-fmt  # auto-format Rust kernel code

# Clippy for kernel Rust
make LLVM=1 rust-clippy  # run clippy on kernel Rust code

Security Implications

  • 65-70% of kernel CVEs are memory safety bugs. Rust eliminates this class for new drivers.
  • Android Binder in Rust: Binder is a high-value attack target. A Rust implementation eliminates UAF and memory corruption bugs that have been the source of multiple Android privilege escalation CVEs.
  • Safe abstractions isolate unsafe. Even if the abstraction layer has a soundness bug, the attack surface for new CVEs in new Rust drivers is dramatically reduced.
  • Rust in the kernel does not eliminate all kernel bugs. Logic errors, race conditions above the unsafe level (e.g., incorrect locking), and hardware bugs are still possible.

Performance Implications

  • Rust kernel code compiles to machine code via LLVM — same backend as C with clang
  • Rust's zero-cost abstractions (iterators, generics) typically compile to identical code as hand-written C
  • RAII (Drop) adds destructor calls that C requires manually — the overhead is the same, but Rust guarantees they are called
  • Arc<T> (reference counting): atomic inc/dec per clone/drop. If a kernel object uses Arc<T> where C would use raw reference counting with manual kref_get()/kref_put(), the overhead is identical.

Modern Usage

  • Linux 6.1+ with CONFIG_RUST=y and CONFIG_SAMPLES_RUST=y for sample modules
  • Android internal tree: Rust Binder, Rust kernel components in active development
  • Google's Android kernel security blog documents ongoing Rust adoption

Future Directions

  • Complete Binder rewrite in Rust: The Android Rust Binder is expected to be merged in a future kernel version
  • File system drivers in Rust: Nova filesystem (simple, educational Rust filesystem) is in development
  • Network drivers in Rust: PHY driver is merged; NIC driver work ongoing
  • Kernel subsystem APIs in Rust: Long-term: VFS, block layer, network stack Rust abstractions
  • Safe C-to-Rust migration tooling: C2Rust (transpiler) + manual cleanup to progressively migrate existing C drivers

Exercises

  1. Set up a Rust kernel development environment (QEMU + Linux 6.6+ with Rust enabled). Build and load the sample Rust module (samples/rust/rust_minimal.rs). Modify it to print additional information and observe the output via dmesg.

  2. Read the PHY driver drivers/net/phy/ax88796b_rust.rs. Compare it to the equivalent C PHY driver. Identify: how many unsafe blocks does the Rust driver contain? How does error handling differ?

  3. Write a simple Rust kernel module that: creates a Mutex<u32>, increments it from multiple kernel threads, and logs the result. Use the kernel Mutex abstraction from kernel::sync.

  4. Read a Linux kernel CVE from 2020-2024 affecting a device driver (e.g., CVE-2022-0185, CVE-2021-4154). Analyze: would Rust's ownership model have prevented the bug? If yes, explain which Rust rule would have been violated. If no, explain what additional mechanism would be needed.

  5. Read Miguel Ojeda's "Rust for Linux" LWN article (LWN.net, 2021). What were the main technical objections raised by kernel developers, and how were they addressed in the final merged implementation?

References

  • Ojeda, Miguel et al. "Rust support." Linux kernel patch series, v5 RFC, 2021.
  • Torvalds, Linus. Linux 6.1 release email, December 2022. lore.kernel.org/lkml
  • Almeida Filho, Wedson; Ojeda, Miguel. "Rust in the Linux Kernel." Linux Plumbers Conference, 2022.
  • Android Security Team. "Memory Safety." android-developers.googleblog.com, 2021.
  • Ryhl, Alice. "Rust in the Linux Kernel." FOSDEM, 2023.
  • Rust for Linux wiki. github.com/Rust-for-Linux/linux/wiki
  • Linux kernel documentation: Documentation/rust/ (in-tree)
  • LWN.net. "Rust in the kernel" article series, 2020-2024. lwn.net/Kernel/Index/#Development_tools-Rust