Ubuntu kernel is getting "Rusty" in Lunar

== Overview ==

One of the changes that has received the most attention in the kernel recently, starting with 6.1, is the introduction of the Rust programming language [1].

This new addition allows developers to create external kernel modules using Rust (as an alternative to C). Such modules can be linked against the Linux kernel and loaded or unloaded like any other “regular” .ko module.

The main advantages of writing modules in Rust are:

  • memory safety (no out of bounds accesses, no use after free, data race safety)
  • strongly typed and statically typed language
  • extremely compact source code

Considering that approximately 70% of the security issues [2] that have a CVE assigned are related to memory safety, incorporating Rust into the kernel development process can be extremely beneficial to provide more secure and resilient kernel code, resulting in a reduction of potential CVEs.

== State of the art ==

Enabling Rust in the kernel and providing the infrastructure to build external modules requires specific versions of the “rustc” compiler and the bindgen utility (used to generate the bindings to the C side of the kernel).

At the time of writing this post the requirements are the following:

  • rustc 1.62.0
  • bindgen 0.56.0
  • clang
  • llvm

The versioning requirements are strict, meaning that we need exactly these versions. The reason is that the kernel relies on certain “unstable” (non-standard) features [3], so some of them are not available in later versions of the “rustc” compiler.

In addition to that, certain kernel functionalities are conflicting with the upstream Rust support. At the time of writing this post, the conflicting features are the following:

  • BTF needs to be disabled (Rust code is not compatible with BPF/BTF)
  • CONFIG_INIT_STACK_ZERO needs to be disabled (clang doesn’t support yet the
    option -ftrivial-auto-var-init=zero)
  • module versions / module signing need to be disabled (symbols from Rust code
    do not fit in the current modversion name limitation of 64 - sizeof(long)
    characters)

== Rust support in the Ubuntu kernel ==

The good news is that you don’t have to deal with all of the requirements above to write your own kernel modules in Rust if you are using Ubuntu.

Ubuntu has done all the work to provide the right toolchain in the distro and custom kernel patches (SAUCE) that allow to relax the kernel constraints mentioned above [4].

As a result, it is possible to write out-of-tree kernel modules in Rust using the generic Ubuntu kernel, simply by installing the required packages and running “make”.

As an example, below there is a template of a simple “hello world” kernel module written in Rust.

To build this module, install the toolchain requirements:

 $ sudo apt update
 $ sudo apt install rustc-1.62 rust-1.62-src rustfmt-1.62 \
   bindgen-0.56 llvm clang gcc make \
   linux-lib-rust-`uname -r` \
   linux-headers-`uname -r`

Create Makefile and hello_rust.rs using the template below and run make:

 $ make
 make[1]: Entering directory '/usr/src/linux-headers-6.2.0-18-generic'
   RUSTC [M] /home/ubuntu/hello_rust.o
   MODPOST /home/ubuntu/Module.symvers
   CC [M]  /home/ubuntu/hello_rust.mod.o
   LD [M]  /home/ubuntu/hello_rust.ko
   BTF [M] /home/ubuntu/hello_rust.ko
 Skipping BTF generation for /home/ubuntu/hello_rust.ko due to unavailability of vmlinux
 make[1]: Leaving directory '/usr/src/linux-headers-6.2.0-18-generic'

At this point you should be able to load/unload the module:

 $ sudo insmod ./hello_rust.ko
 $ sudo dmesg | tail -1
 [   53.191032] hello_rust: Hello from Rust
 $ sudo rmmod hello_rust
 $ sudo dmesg | tail -1
 [   74.054869] hello_rust: Goodbye from Rust

Makefile

NAME=hello_rust

ifndef KERNELRELEASE
ifndef KDIR
KDIR:=/lib/modules/`uname -r`/build
endif
PWD := $(shell pwd)

rust_flags=CROSS_COMPILE=x86_64-linux-gnu- HOSTRUSTC=rustc-1.62 RUSTC=rustc-1.62 BINDGEN=bindgen-0.56 RUSTFMT=rustfmt-1.62 RUST_LIB_SRC=/usr/src/rustc-1.62.1/library

all:
    @$(MAKE) $(rust_flags) -C $(KDIR) M=$(PWD) modules

install:
    @$(MAKE) $(rust_flags) -C $(KDIR) M=$(PWD) modules_install

clean:
    @rm -f *.o *.ko *.mod* .*.cmd *.d Module.symvers modules.order
    @rm -rf .tmp_versions

else
obj-m := $(NAME).o
endif

hello_rust.rs

// SPDX-License-Identifier: GPL-2.0

//! Rust hello world example.

use kernel::prelude::*;

module! {
    type: HelloRust,
    name: "hello_rust",
    author: "Andrea Righi <andrea.righi@canonical.com>",
    description: "Rust hello world example",
    license: "GPL",
}

struct HelloRust {
}

impl kernel::Module for HelloRust {
    fn init(_module: &'static ThisModule) -> Result<Self> {
        pr_info!("Hello from Rust\n");
        Ok(HelloRust { })
    }
}

impl Drop for HelloRust {
    fn drop(&mut self) {
        pr_info!("Goodbye from Rust\n");
    }
}

== Conclusion ==

So, the question is: what can I do right now with Rust?

Short answer: not much. Upstream support for Rust at the moment is very minimal, Linus requested very clearly to apply something that could just do “hello world” and that is literally what we got in 6.1.

However, a lot more Rust kernel code has been published and 6.2 has got a few more additional features [5].

Even more features are likely to get merged in the next kernels, to a point when we will be able to write kernel Rust modules usable in real-world scenarios, such as fully functional device drivers, network protocols, in-kernel servers, and so on.

For the reasons mentioned above, Rust support in the Ubuntu kernel should be considered like a “technology preview” for now, not a feature ready for production and at the moment we only have plans to support this in Ubuntu 23.04 ‘Lunar Lobster’.

In conclusion, Ubuntu can now be used by all the developers that want to easily and quickly begin to familiarize themselves with kernel programming in Rust, without dealing with external software/packages and simply using the standard Ubuntu kernel and toolchain.

== References ==

[1] https://lwn.net/Articles/910762/
[2] https://alexgaynor.net/2020/may/27/science-on-memory-unsafety-and-security/
[3] https://github.com/Rust-for-Linux/linux/issues/2
[4] https://bugs.launchpad.net/bugs/2007654
[5] https://lwn.net/Articles/914458/

20 Likes

Thanks @arighi for sharing this small how-to :+1:

2 Likes

I’ve followed the directions here, using Ubuntu 23.04, but when attempting to compile the out of tree module I’m getting an error about a missing target.json file. Has something changed between 6.2.0-18 and 6.2.0-23?

Yes, there is a change, we’ve introduced a new package called linux-lib-rust that provides all the kernel headers and artifacts to be able to compile Rust modules. You need to install this package and your out-of-tree module should build.

The main motivation for the separate linux-lib-rust is that we didn’t want to increase the size of linux-headers unnecessary, this allows to save storage, save some energy and people that are interested to do experiments with Rust can simply install this additional package.

I was trying to modify the post, but it looks like it’s read-only now… I hope people that are interested will read this comment… :slight_smile:

2 Likes

Thanks for the tip and the explanation. I’d actually discovered the missing rust headers on my own and found the linux-lib-rust package, but the context is useful!

1 Like