Device drivers on Linux-powered embedded or IoT systems execute in kernel space thus must be fully trusted. Any fault in drivers may significantly impact the whole system. However, third-party embedded hardware manufacturers usually ship their proprietary device drivers with their embedded devices. These out-of-tree device drivers are generally of poor quality because of a lack of code audit.
We propose an approach that helps third-party developers to improve the reliability and safety of device drivers without modifying the kernel: Rewriting device drivers in a memory-safe programming language called Rust. Rust's rigorous language model assists the device driver developers to detect many security issues at compile time. We designed a framework to help developers to quickly build device drivers in Rust. We also utilized Rust’s security features to provide several useful infrastructures for developers so that they can easily handle kernel memory allocation and concurrency management, at the same time, some common bugs (e.g. use-after-free) can be alleviated.
We demonstrate the generality of our framework by implementing a real-world device driver on Raspberry Pi 3, and our evaluation shows that device drivers generated by our framework have acceptable binary size for canonical embedded systems and the runtime overhead is negligible.
More details about the design and implementation can be found in our paper: Securing the Device Drivers of Your Embedded Systems: Framework and Prototype.
-
x86_64
Rust nightly. Tested on
nightly-2018-09-30-x86_64-unknown-linux-gnu
. -
ARMv7
(Raspberry Pi)Rust nightly. In addition, you need to install the new target:
$ rustup target add arm-unknown-linux-gnueabi
And the
arm-linux-gnueabihf-
cross-compiler.
A pre-built kernel (with configuration and header files) is needed.
-
x86_64
Your Linux distribution should provide a package for this. For example, on Ubuntu, you can try:
$ sudo apt-get install linux-headers-`uname -r`
-
ARMv7
(Raspberry Pi)You need to compile your own kernel in order for
bindgen
to work.
cargo-xbuild
,rust-src
andrustfmt-preview
$ cargo install cargo-xbuild
$ rustup component add --toolchain=nightly rust-src
$ rustup component add rustfmt-preview
- Select an example
$ cd hello_world
-
Compile into a static library
x86_64
$ RUST_TARGET_PATH=$(pwd)/.. cargo xbuild --target x86_64-linux-kernel-module
ARMv7
(Raspberry Pi)$ RUST_TARGET_PATH=$(pwd)/.. KDIR=<path-to-your-compiled-kernel> cargo xbuild --target armv7l-linux-kernel-module
-
Link as a kernel module
x86_64
$ make
ARMv7
(Raspberry Pi)$ make TARGET=armv7l-linux-kernel-module KDIR=<path-to-your-compiled-kernel> CROSS=arm-linux-gnueabihf-
-
Load and test
See below.
-
If you want to clean it up
x86_64
$ make clean;cargo clean
ARMv7
(Raspberry Pi)$ make clean TARGET=armv7l-linux-kernel-module KDIR=<path-to-your-compiled-kernel> CROSS=arm-linux-gnueabihf-;cargo clean
Examples are tested on Ubuntu 18.04 (Linux kernel 4.15.0-46-generic), smsc95xx
is tested on Raspberry Pi 3 (Linux kernel 4.19.29).
The simplest kernel module. It just prints "hello" and "goodbye".
$ sudo insmod helloworld.ko # load the module
$ sudo rmmod helloworld # remove the module
$ dmesg # dump kernel messages
A simple character device which is similar to the yes
Unix command.
$ sudo insmod yes_chardev.ko
$ cat /proc/devices # find the major number of the device 'yes', for example, 243
$ sudo mknod /dev/yes c 243 0 # make a filesystem node (replace 243 with your own major number)
$ sudo cat /dev/yes # read from the device
$ sudo rmmod yes_chardev
A simple sysctl device driver.
$ sudo insmod simple_sysctl.ko
$ cat /proc/sys/rust/example/test # the default value should be 1
$ sudo sh -c "echo 2 > /proc/sys/rust/example/test" # change the value
$ cat /proc/sys/rust/example/test # now the value is 2
$ sudo rmmod simple_sysctl
There is another way to read/write the sysctl value:
$ sysctl rust.example.test # read
$ sudo sysctl -w rust.example.test=2 # write
A simple example to illustrate the use of Spinlock
and Mutex
.
let mutex_data = sync::Mutex::new(50);
let mut data = mutex_data.lock();
println!("Data {} is locked by a mutex", *data);
*data = 100;
println!("Now data is {}", *data);
println!("Hello from Rust!");
The above code snippet will output like this:
[ 424.328154] Mutex is locked!
[ 424.328156] Data 50 is locked by a mutex
[ 424.328158] Now data is 100
[ 424.328158] Hello from Rust!
[ 424.328160] Mutex is dropped!
A highly simplified real-world device driver for LAN9512 USB to Ethernet controller, which is used on Raspberry Pi 3. The implementation resembles the C version.
The efforts of writing kernel modules in Rust can be traced back to 2013 (the first commit of rust.ko), long before Rust's first stable version was released. Here we list some of the objectives that people have already achieved and what we plan to achieve in the future.
- Generate OS-independent machine code by using JSON specification files
- Recompile pre-compiled libraries (core, compiler_builtins, alloc) by using cargo-xbuild
- Generate Rust bindings for kernel headers to reuse existing data structures and functions defined inside the kernel by using bindgen.
- Kernel allocator by using the GlobalAlloc trait.
- Kernel synchronizations by reimplementing the synchronization primitives.
- A simple real-world device driver for LAN9512.
- Minimize the use of unsafe Rust.
- Find a idiomatic way to define callback functions.
- Failure recovery.
- Use static analysis tool to reason about unsafe Rust code.
Thanks to these previous works on writing Linux kernel driver in Rust. Their attempts inspire us a lot.
fishinabarrel/linux-kernel-module-rust
: https://github.com/fishinabarrel/linux-kernel-module-rusttsgates/rust.ko
: https://github.com/tsgates/rust.kokernel-roulette
: https://github.com/souvik1997/kernel-roulette
GPL-2.0