Skip to main content
โšก Calmops

Rust for Systems Programming: Linux Kernel Modules

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