In recent years, the Rust language has gained a lot of developer attention for its memory safety, high reliability, and zero abstraction capabilities, which happen to be needed in kernel programming, so let’s try out how to write Linux kernel modules in rust.

Rust and kernel modules

Although Rust support has been merged into the mainline in Linux Kernel version 6.1, so in theory, developers can use Rust to write kernel modules for Linux 6.1. However, in actual development work, the kernel version is not the latest, for example, the kernel of Debian 11 is version 5.10, so in this case, how to write kernel modules with Rust?

Principle

  1. how Rust registers callbacks with the kernel, how to call kernel code. interoperability of Rust and C
  2. how to compile Rust to the target platform. target configuration of Rust
  3. how Rust declares kernel module entries and adds special sections. binary conventions for Rust kernel modules

Interoperability of Rust and C

The first issue is basically the interoperability of C and Rust. Thanks to Rust’s abstraction level, it is easy to call C and Rust from each other. rust also provides an official library like bindgen that generates .rs files from .h files. In this way, it seems that it is possible to use bindgen to translate kernel headers to .rs directly? But there is still a problem, how to get the kernel header path? You can use a dummy kernel module to derive the compilation parameters during the compilation process, which contains the header path, compilation parameters, etc., for bindgen to generate the code.

Rust’s target configuration

The main differences between kernel modules and normal programs are.

  1. the kernel module is freestanding, no libc, memory allocation is also relatively primitive
  2. the kernel module has special conventions for exception handling, etc.

rust provides a no_std mechanism that allows rust code to be compiled into freestanding binary; rust also provides a way to customize the target, so you can customize the declaration to generate the binary specification.

Binary conventions for kernel modules

The kernel has a number of conventions for kernel modules.

  • Declaring module information through sections such as .modinfo
  • provide init_module, cleanup_module to provide kernel module installation and uninstallation functions

In this piece, rust provides link_section to customize the section, and also supports extern “C” to export functions. In addition, these underlying operations can be simplified by the kernel providing some C macros, and Rust also provides macros that can be used to do similar things.

A small example

Having said that, let’s look at an example with comments.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#![no_std]
// no_std is used to indicate that there is no std library, i.e. freestanding environment
extern crate alloc;

use alloc::borrow::ToOwned;
use alloc::string::String;

// We use printk as the base layer and provide println
use linux_kernel_module::println;

// This struct represents the kernel module
struct HelloWorldModule {
    message: String,
}

// Implementing the kernel module initialization method
impl linux_kernel_module::KernelModule for HelloWorldModule {
    fn init() -> linux_kernel_module::KernelResult<Self> {
        println!("Hello kernel module from rust!");
        Ok(HelloWorldModule {
            message: "on the heap!".to_owned(),
        })
    }
}

// Provide kernel module uninstall method
impl Drop for HelloWorldModule {
    fn drop(&mut self) {
        println!("My message is {}", self.message);
        println!("Goodbye kernel module from rust!");
    }
}

// With the kernel_module macro, information about the kernel module is exported
linux_kernel_module::kernel_module!(
    HelloWorldModule,
    author: b"Fish in a Barrel Contributors",
    description: b"An extremely simple kernel module",
    license: b"GPL"
);

Specifically, how is it built and run?

1
2
3
4
5
6
7
8
9
$ cd linux-kernel-module-rust/hello-world
$ RUST_TARGET_PATH=$(pwd)/.. cargo +nightly xbuild --target x86_64-linux-kernel-module
$ make
$ insmod helloworld.ko
$ rmmod helloworld
$ dmesg | tail -n 3
[521088.916091] Hello kernel module from rust!
[521174.204889] My message is on the heap!
[521174.204891] Goodbye kernel module from rust!

rust

It has been tested on kernel 5.10.0-17-amd64.

For the specific code and related configuration, you can refer to the GitHub repository: https://github.com/robberphex/linux-kernel-module-rust

Some small details

  • VSCode support

    Since rust-analyzer does not have enough support for custom targets and multiple modules, we temporarily need to manually configure settings.json to develop properly.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    // ./.vscode/settings.json
    {
        "rust-analyzer.cargo.extraEnv": {
            "RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
        },
        "rust-analyzer.cargo.target": "x86_64-linux-kernel-module",
        "rust-analyzer.server.extraEnv": {
            "RA_LOG": "lsp_server=debug",
            "RUST_TARGET_PATH": "/root/linux-kernel-module-rust"
        },
        "rust-analyzer.trace.server": "verbose",
        "rust-analyzer.linkedProjects": [
            "hello-world/Cargo.toml",
            "Cargo.toml"
        ],
    }
    
  • Other advanced functions

    such as character devices, sysctl, and other functions, can be found in the relevant test code in Project.

More planning

The original project was fishinabarrel/linux-kernel-module-rust, but it is currently prompted to use rust-for-linux, which has been archived.

However, considering that there are still a lot of older kernels out there, I have re-fixed some of the environments in this project to allow people to write kernel modules in Rust on older kernels.

Ref