Skip to main content

Rust for Systems Programming: Linux Kernel Modules

Created: January 1, 0001 Larry Qu 5 min read

Introduction

Rust is officially supported in the Linux kernel starting with version 6.1. This opens new possibilities for systems programming, drivers, and kernel extensions—all with Rust’s safety guarantees.

This guide covers building Linux kernel modules in Rust.


Why Rust for Kernel Programming?

Safety in Kernel Code

Traditional C kernel code risks:
❌ Buffer overflows → arbitrary code execution
❌ Use-after-free → system crashes
❌ Data races → kernel panics
❌ Integer overflows → security vulnerabilities

Rust in kernel:
✅ Memory safety at compile time
✅ Thread safety guarantees
✅ Integer overflow checks
✅ No undefined behavior

Real-World Impact

Linux kernel bugs by year:
Year    Total Bugs    Memory Bugs    % Memory
────────────────────────────────────────────
2015    521           229            44%
2016    445           198            45%
2017    514           237            46%
2018    513           251            49%

Rust could eliminate ~45% of kernel bugs!

Setting Up Rust Kernel Development

Prerequisites

# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup default nightly

# Install kernel sources
git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
cd linux

# Check Rust support
grep CONFIG_RUST .config
# CONFIG_RUST=y  (or =m for modules)

Build Requirements

# Rust compiler for kernel
rustc --version  # Must be nightly

# Additional tools
rustup component add rust-src
cargo install bindgen

# Kernel build tools
sudo apt install build-essential linux-headers-$(uname -r)

Hello World Kernel Module

Module Structure

// hello.rs
#![no_std]
#![feature(never_type)]

use kernel::prelude::*;

module! {
    type: HelloWorld,
    name: "hello_world",
    author: "Your Name",
    license: "GPL",
    description: "A simple Hello World kernel module",
}

struct HelloWorld;

impl kernel::module::Module for HelloWorld {
    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        println!("Hello from Rust kernel module!");
        Ok(HelloWorld)
    }
}

impl Drop for HelloWorld {
    fn drop(&mut self) {
        println!("Goodbye from Rust kernel module!");
    }
}

Building the Module

# Add to Kconfig
config HELLO_RUST
    tristate "Hello World Rust Module"
    help
        A simple hello world module written in Rust

# Add to Makefile
obj-$(CONFIG_HELLO_RUST) += hello.o
hello-y := hello.rs

# Build
make modules LLVM=1
insmod hello.ko

Testing

# Check loaded modules
lsmod | grep hello_world
# hello_world            16384  0

# Check kernel messages
dmesg
# [12345.678901] Hello from Rust kernel module!

# Unload
rmmod hello_world
# [12345.789012] Goodbye from Rust kernel module!

Character Device Driver

Device Registration

use kernel::prelude::*;
use kernel::file_operations::FileOperations;
use kernel::miscdev::MiscDevice;

module! {
    type: CharDevice,
    name: "rust_char_dev",
    author: "Your Name",
    license: "GPL",
    description: "Character device driver in Rust",
}

struct CharDeviceOps;

impl FileOperations for CharDeviceOps {
    type Data = ();
    type OpenData = ();

    fn open(_context: &(), _file: &kernel::file::File) -> Result<()> {
        println!("Device opened");
        Ok(())
    }

    fn release(_context: &()) {
        println!("Device closed");
    }
}

struct CharDevice {
    _miscdev: MiscDevice,
}

impl kernel::module::Module for CharDevice {
    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        let miscdev = MiscDevice::new(
            CharDeviceOps,
            (),
            c_str!("rust_device"),
        )?;
        
        println!("Character device created");
        Ok(CharDevice { _miscdev: miscdev })
    }
}

impl Drop for CharDevice {
    fn drop(&mut self) {
        println!("Character device destroyed");
    }
}

Memory and Data Structures

Safe Kernel Allocations

use kernel::alloc::BoxExt;
use kernel::prelude::*;

pub fn allocate_example() -> Result<()> {
    // Allocate a boxed i32
    let boxed = Box::try_new(42)?;
    println!("Value: {}", *boxed);
    
    // Automatically freed when dropped
    Ok(())
}

pub fn allocate_vec() -> Result<()> {
    // Allocate vector
    let mut vec: Vec<i32> = Vec::new();
    vec.push(1)?;
    vec.push(2)?;
    vec.push(3)?;
    
    println!("Vector: {:?}", vec.len());
    Ok(())
}

Kernel Data Structures

use kernel::sync::{Arc, Mutex};
use kernel::prelude::*;

pub struct DeviceState {
    counter: u32,
    name: CString,
}

pub fn create_state() -> Result<()> {
    let state = Arc::try_new(Mutex::new(DeviceState {
        counter: 0,
        name: CString::try_from_fmt(fmt!("device"))?,
    }))?;

    {
        let mut guard = state.lock();
        guard.counter += 1;
        println!("Counter: {}", guard.counter);
    }

    Ok(())
}

Synchronization Primitives

Mutex

use kernel::sync::Mutex;

pub fn mutex_example() -> Result<()> {
    let counter = Mutex::new(0);
    
    {
        let mut c = counter.lock();
        *c += 1;
    }
    
    println!("Count: {}", *counter.lock());
    Ok(())
}

Spinlock

use kernel::sync::SpinLock;

pub fn spinlock_example() -> Result<()> {
    let value = SpinLock::new(42);
    
    {
        let mut v = value.lock();
        *v = 100;
    }
    
    println!("Value: {}", *value.lock());
    Ok(())
}

Interrupt Handling

IRQ Handler Registration

use kernel::irq::{self, IrqHandler};
use kernel::prelude::*;

struct IrqData {
    count: u32,
}

impl IrqHandler for IrqData {
    type Data = ();

    fn handle_irq(_: &Self::Data) -> irq::IrqReturn {
        println!("Interrupt received");
        irq::IrqReturn::Handled
    }
}

pub fn register_handler() -> Result<()> {
    // Register IRQ handler
    // irq::request_irq(IRQ_NUM, &handler)?;
    Ok(())
}

System Call Interface

Exporting Kernel Symbols

use kernel::prelude::*;
use kernel::module;

pub fn exported_function(value: i32) -> i32 {
    value * 2
}

module_export!();

#[export_name = "rust_exported_function"]
pub fn exported(value: i32) -> i32 {
    exported_function(value)
}

Real-World Example: Simple Counter Device

#![no_std]

use kernel::prelude::*;
use kernel::file::{File, SeekFrom};
use kernel::file_operations::FileOperations;
use kernel::miscdev::MiscDevice;
use kernel::sync::Mutex;

module! {
    type: CounterDevice,
    name: "rust_counter",
    author: "Kernel Developer",
    license: "GPL",
    description: "A simple counter device in Rust",
}

struct CounterData {
    counter: Mutex<i32>,
}

struct CounterOps;

impl FileOperations for CounterOps {
    type Data = CounterData;
    type OpenData = ();

    fn open(_context: &(), _file: &File) -> Result<()> {
        pr_info!("Counter device opened\n");
        Ok(())
    }

    fn release(_context: &CounterData) {
        pr_info!("Counter device closed\n");
    }
}

struct CounterDevice {
    _miscdev: MiscDevice,
}

impl kernel::module::Module for CounterDevice {
    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
        let data = CounterData {
            counter: Mutex::new(0),
        };

        let miscdev = MiscDevice::new(
            CounterOps,
            data,
            c_str!("rust_counter"),
        )?;

        pr_info!("Counter module loaded\n");
        Ok(CounterDevice { _miscdev: miscdev })
    }
}

impl Drop for CounterDevice {
    fn drop(&mut self) {
        pr_info!("Counter module unloaded\n");
    }
}

Debugging Kernel Modules

Logging

use kernel::prelude::*;

// Different log levels
pr_info!("Information message\n");
pr_warn!("Warning message\n");
pr_err!("Error message\n");
pr_debug!("Debug message (if CONFIG_DEBUG enabled)\n");

// String formatting
let value = 42;
pr_info!("Value: {}\n", value);

GDB Debugging

# Build with debug info
make modules DEBUG=1

# Load module with GDB
gdb vmlinux
(gdb) target remote :1234  # Connect to QEMU
(gdb) add-symbol-file hello.ko 0xXXXX
(gdb) break hello_init
(gdb) continue

Best Practices

✅ Use kernel provided allocators
✅ Always check Result types
✅ Use Mutex for shared state
✅ Document safety requirements
✅ Test thoroughly in VMs
✅ Follow kernel style guidelines
✅ Use pr_info for tracing

Limitations

Current limitations in Rust for Linux:
❌ No floating-point support
❌ Limited inline assembly support
❌ No Rust standard library
❌ Async/await limited
✅ But rapidly improving with each kernel release

Glossary

  • Module: Loadable kernel object (.ko file)
  • Character Device: Device providing byte-stream interface
  • Spinlock: Lock for interrupt contexts
  • IRQ: Interrupt request handler
  • pr_info: Kernel logging macro

Resources


Comments

Share this article

Scan to read on mobile