0% found this document useful (0 votes)
40 views

Linux Kernel Module Development With Rust

Uploaded by

lisansprogram
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
40 views

Linux Kernel Module Development With Rust

Uploaded by

lisansprogram
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 2

Linux Kernel Module Development with Rust

Shao-Fu Chen Yu-Sung Wu


Department of Computer Science Department of Computer Science
National Yang Ming Chiao Tung University National Yang Ming Chiao Tung University
Hsinchu City, Taiwan Hsinchu City, Taiwan
[email protected] [email protected]

Abstract—The Linux system has become an indispensable 2) __param section


component of today’s Internet services, network backbones, and This section provides the module parameters information
IoT devices. The Linux kernel is primarily implemented in the to the kernel. Each parameter is a C language
C language for efficiency, creating opportunities for memory kernel_param structure, which contains the parameter's
bugs and synchronization bugs. We introduce the use of the Rust
name, data, permission flag, and operators. We can use
programming language in kernel development, where the safety
“#[repr(C)]” in Rust to tell the compiler to generate a C
features of the Rust language are leveraged to prevent the
introduction of memory bugs or synchronization bugs when language compatible layout for kernel_param.
writing kernel code. We showcase the key steps in developing a #[link_section = ".modinfo"]
Linux kernel module in Rust and discuss how the memory bugs #[used]
pub static __modinfo_name: [u8; 14] = *b"name=rs_hello\0";
and synchronization bugs are prevented. The evaluation #[link_section = ".modinfo"]
demonstrates that the performance overhead of the Rust kernel #[used]
pub static __modinfo_author: [u8; 16] = *b"author=Leo Chen\0";
modules is on par with the C kernel modules.

Keywords—Linux kernel, Rust, memory bug, synchronization Fig. 1. Example .modinfo section in Rust
2022 IEEE Conference on Dependable and Secure Computing (DSC) | 978-1-6654-2141-6/22/$31.00 ©2022 IEEE | DOI: 10.1109/DSC54232.2022.9888822

bug, compile-time check


III. APPLY SAFETY WRAPPER
I. INTRODUCTION When a Rust-based kernel module needs to interact with
The Linux operating system has been a de facto building existing kernel functions and data structures implemented in
block of the Internet. As a general-purpose operating system, the C language, the memory access and the acquisition of
the Linux kernel is implemented in the C language to achieve synchronization locks must be regulated by the safety wrapper
the efficiency needed for powering a wide range of today’s structures in the Rust-for-Linux kernel tree. In the following,
most essential applications ranging from large-scale cloud we give two examples of using the safety wrapper.
services to power-constrained IoT devices. However, the lack
unsafe extern "C" fn read_callback<T: FileOperations>(
of safety checks in the C programming language makes it quite file: *mut bindings::file,
challenging to eradicate memory bugs and synchronization buf: *mut c_types::c_char,
len: c_types::c_size_t,
bugs in the kernel [1]. offset: *mut bindings::loff_t,
) -> c_types::c_ssize_t {
In this paper, we leverage the Rust-for-Linux [2] from_kernel_result! {
let mut data = unsafe {
infrastructure in the Linux kernel source tree to develop Linux UserSlicePtr::new(buf as *mut c_types::c_void, len)
.writer()
kernel modules in Rust. With the built-in safety checks in Rust, };
the kernel modules are free of memory bugs and let f = unsafe { T::Wrapper::borrow((*file).private_dat
synchronization bugs by design. We also conduct experiments a) };
to demonstrate that the performance overhead of the Rust- let read = T::read(
based kernel modules is on par with the kernel modules &f,
unsafe { &FileRef::from_ptr(file) },
implemented in the C language. &mut data,
unsafe { *offset }.try_into()?
)?;
II. CONSTRUCTION OF KERNEL MODULE WITH RUST unsafe { (*offset) += bindings::loff_t::try_from(read).
unwrap() };
The Rust Programming Language enforces Object Ok(read as _)
Ownership, Reference and Borrow Checker, and Multithread }
}

Concurrency [3] through compile-time checks. Essentially,


this prevents the introduction of memory bugs or Fig. 2. Rust read file operation callback function
synchronization bugs. As the checks are done at compile time,
programs written in Rust can achieve the same level of A. Callback functions
performance as in C. To create a loadable kernel module in
Linux kernel uses callback functions in many places. The
Rust, we will prepare the corresponding sections in the module
most widely used one is struct file_operations,
as follows.
which is used to register the operations of devices. Fig. 2
1) .modinfo section shows the wrapper function for the corresponding Rust
This section contains basic information about a module, callback function for the read operation. First, it extracts the
including name, description, authors, license, etc. The Rust data structure from the private data field. Then, it stores
information is represented in null-terminated strings. To the kernel data structure struct file into a Rust opaque
construct this section, we only need to declare the information type’s private field to protect it. The kernel module developers
in null-terminated strings and use special attributes to inform can only access the values exposed by the wrapper type. Fields
the Rust compiler to link the information into the .modinfo that should not be accessed by the kernel modules, such as
section, as shown in Fig. 1. epoll-related data, cannot be seen since the Rust compiler
forbids the access to the private data structure. Userspace
memory access needs to go through the UserSlicePtr in
the wrapper. As shown in Fig. 2, the UserSlicePtr

978-1-6654-2141-6/22/$31.00 © 2022 IEEE


Authorized licensed use limited to: DUBLIN CITY UNIVERSITY. Downloaded on October 27,2023 at 15:32:48 UTC from IEEE Xplore. Restrictions apply.
enforces writes to userspace memory to go through TABLE I. WRITING TEST FOR /dev/null DEVICE
copy_to_user(). Finally, the wrapper passes control to Data size Block size C version Rust version
the Rust-based callback function read and returns its value. 4 GB 512 Bytes 764.90 MiB/s 743.38 MiB/s
16 GB 512 Bytes 760.53 MiB/s 750.63 MiB/s
impl<T: ?Sized> Mutex<T> {
pub fn lock(&self) -> Guard<'_, Self> {
16 GB 4 KB 6129.44 MiB/s 6077.15 MiB/s
self.lock_noguard(); 4 GB 64 Bytes 90.557 MiB/s 88.179 MiB/s
unsafe { Guard::new(self, ()) }
}
} TABLE II. READING TEST FOR /dev/urandom DEVICE
impl<T: ?Sized> Lock for Mutex<T> { Data size Block size C version Rust version
type Inner = T;
type GuardContext = (); 4 GB 512 Bytes 172.47 MiB/s 157.07 MiB/s
4 GB 4 KB 242.15 MiB/s 196.93 MiB/s
fn lock_noguard(&self) {
unsafe { 512 MB 512 Bytes 169.14 MiB/s 154.36 MiB/s
bindings::mutex_lock(self.mutex.get());
}
}
The results show some performance loss for the kernel
unsafe fn unlock(&self, _: &mut ()) {
unsafe { bindings::mutex_unlock(self.mutex.get()) };
modules written in Rust. The overhead on the write speed of
} /dev/null is negligible because the write function in the
fn locked_data(&self) -> &UnsafeCell<T> { file operations for the /dev/null implementation only
}
&self.data
needs to return the buffer length without making any actual
} data transfer. The slight performance loss might be caused by
impl<L: Lock + ?Sized> Drop for Guard<'_, L> { the wrapper function.
fn drop(&mut self) {
unsafe { self.lock.unlock(&mut self.context) }; Reading from the /dev/urandom device involves actual
}
} data transfer, and the performance overhead is relatively
higher. The overhead becomes even more significant when we
Fig. 3. Kernel mutex lock integration increase the data size (the total length of the random bytes to
be read) because the Rust version uses the Rust API
B. Kernel Lock Integration get_random_bytes() function with a fixed 256 bytes
Most of the synchronization primitives in the Linux kernel buffer to generate the random bytes. This is inherently less
use a linked list to implement the wait list and track deadlocks efficient than the kernel built-in extract_crng_user()
[4]. The self-referential pointer in a wait list will become API used by the C version of the /dev/urandom device for
invalid if the data structure is moved to another location. To generating random bytes. The effect of the inefficiency rises
solve this issue, the data structure of the kernel as we increase the data size.
synchronization primitive needs to be pinned (wrapped in Rust
Pin type). For instance, we will first create an uninitialized V. CONCLUSION
mutex variable, pin it, and initialize the mutex with the kernel Rust ensures memory safety with compile-time checks.
synchronization primitive API. The developers have to write The wrapper enables the development of Rust-based kernel
unsafe code to create the uninitialized mutex, followed by a modules by providing an interface to the underlying kernel
series of boilerplate code. This may discourage developing APIs and ensures the operations adhere to the memory safety
kernel modules in Rust and is one of the unsolved issues in the requirements. As kernel code are privileged, a minor bug can
current Rust kernel module development process. lead to catastrophic disaster to a system. Even though the
After taking care of the self-referential issue, the remaining current prototype is still in its infancy, we believe the use of
steps in the wrapper for kernel synchronization primitive are Rust programming in the Linux kernel will profoundly impact
straightforward. To acquire a lock, the wrapper calls the kernel the reliability and security of the Linux kernel. In fact, the
lock function and creates a guard object, which will be used to kernel developers are considering using Rust to implement an
access the content protected by the lock. When the guard interface for the kernel drivers [5]. By using a safe and modern
object is dropped, the wrapper automatically calls the lock’s language, they hope that more people will get involved in the
unlock function. project and improve the maintainability of the codebase.

IV. EVALUATION ACKNOWLEDGMENT


To understand if the Rust-based kernel modules with The work was supported by the Ministry of Science and
memory safety can achieve similar performance as the kernel Technology of the Republic of China under grant 110-2628-
modules implemented in the C language, we implemented E-A49 -004.
two character devices /dev/null and /dev/urandom in REFERENCES
Rust and compared their data transfer throughputs to the C- [1] A. Nguyen. "CVE-2021-22555: Turning \x00\x00 into 10000$."
based implementations in the Linux kernel. The write speeds https://google.github.io/security-research/pocs/linux/cve-2021-
of the /dev/null devices are summarized in TABLE I. The 22555/writeup.html (accessed 12-1, 2021).
read speeds of the /dev/urandom devices are summarized [2] "Rust-for-Linux." https://github.com/Rust-for-Linux/linux (accessed
in TABLE II. The experiment results are averaged over 10 12-29, 2021).
[3] S. Klabnik and C. Nichols, The Rust Programming Language. No
experiment rounds. Starch Press, 2019.
[4] "Safe initialization of pinned structs." https://github.com/Rust-for-
Linux/linux/issues/290 (accessed 12-1, 2021).
[5] M. Ojeda. "[PATCH 00/13] [RFC] Rust support."
https://lkml.org/lkml/2021/4/14/1023 (accessed 12-1, 2021).

Authorized licensed use limited to: DUBLIN CITY UNIVERSITY. Downloaded on October 27,2023 at 15:32:48 UTC from IEEE Xplore. Restrictions apply.

You might also like