top | item 20833639

Linux kernel drivers in Rust might become an option in the future

372 points| jobstijl | 6 years ago |lwn.net | reply

254 comments

order
[+] geofft|6 years ago|reply
We're working on it: https://github.com/fishinabarrel/linux-kernel-module-rust

Check out the demo in PR #122, which lets you create three boolean sysctls and a character device that prints the state of those sysctls in JSON (using serde).

We gave a talk about it last week at Linux Security Summit, I'll submit it once the recording is up :) Slides are at https://ldpreload.com/p/kernel-modules-in-rust-lssna2019.pdf .

[+] zaphirplane|6 years ago|reply
For those like I was, wondering why a framework is required to write out of tree Linux modules in rust

It’s to auto create rust bindings. Link between rust crates and kernel build system

[+] raverbashing|6 years ago|reply
Looks interesting and definitely promising

Though it would probably make sense to make the API more "Rust like" and avoid things like the multiple cstr!()

Some things are definitely trickier than others, for example how to deal with the different options of kmalloc if it's rust that's allocating memory

[+] steelframe|6 years ago|reply
I attended LSS this year and enjoyed your talk. I also attended KP Singh's talk on eBPF LSMs, and I couldn't help but compare the two mechanisms for making code in the kernel more safe.

eBPF's approach is to generate code that's verified to be safe and that's limited in the operations it's allowed to do in the kernel, using trusted helper functions to access kernel data structures and functions. I couldn't help but draw an analogy to unsafe wrappers for Rust.

In its current form I might not want to write core logic of a device driver in eBPF, as it started out as something suited for mandatory access control policy, packet filtering, and auditing. That's changing though, and people seem to be demanding more and more functionality in eBPF. I'm admittedly bad at predicting the future, but one thing I can say with some confidence is that eBPF's capabilities are going to increase with time.

I would also be hesitant to attempt to write a device driver in Rust because of the degree to which I'd have to interact with other subsystems and structures of the kernel that are still written in unsafe C. I wouldn't have an intuition for the incremental benefit of using Rust for some of the core logic of the driver while still having to use unsafe wrappers to muck with all the other parts of the kernel where, if I get it wrong, my driver can still oops/hang/etc. Would the complexity of mixing two languages with unsafe wrappers plus increased code size "pay for" any incremental benefit, or would I have to expect future dividends from eventually having "enough" of the kernel written in Rust for it to be a worthwhile investment?

One advantage of eBPF, I suppose, is that you can write your code in old familiar C. With forthcoming support for bounded loops, I expect people are going to be proposing more and more use cases for it. I can imagine scenarios where one camp will say, "This is a job for Rust," and the other camp will retort, "Actually, this job can currently be done in eBPF." eBPF having the other distinct advantage of already being a supported feature of the upstream kernel.

This is all pretty new to me, and so all I have are my impressions and intuitions. I'd appreciate hearing more perspectives on this.

[+] pjmlp|6 years ago|reply
Thanks for the heads up about this year's Summit.
[+] ufmace|6 years ago|reply
Sounds like the start of something good. Though the comments feel a bit weird. Maybe it's just a strange day for some reason, but I feel like today, I've seen an awful lot of comments from people pushing rewriting things in Rust who know absolutely nothing about the code they're asking about or the problem domain it runs in.

I mean, I personally think Rust is cool and all that, but come on, who does that?

[+] JoshTriplett|6 years ago|reply
For the record, as one of the developers on the Rust project: we consider that kind of over-the-top evangelism counterproductive and unwelcome. Note that in the talk that inspired this news, I specifically said that I wasn't there to push Rust, just there to explain how I'm working to make Rust an option for more people and projects.

Even if you want people to use more Rust, that kind of aggressive evangelism doesn't serve you well, and people find it off-putting.

[+] masklinn|6 years ago|reply
> I mean, I personally think Rust is cool and all that, but come on, who does that?

Some people have an LDS complex (they've found their messiah and want to spread the word). Apparently there's also people who've decided that over-the-top inane "evangelisation" was a good troll and way to turn people off. A significant number of PL threads on /r/programming have a comment attempting this sort of shit-stirring.

[+] gameswithgo|6 years ago|reply
This particular linux thing wouldn't involve rewriting anything in Rust.
[+] jkelleyrtp|6 years ago|reply
While all code shouldn't be rewritten in the "next-best-thing," there might be merit in rewriting some bug-prone code that expands the security vulnerability surface area. The borrowchecker and other safety mechanisms can help new and potentially unsecure code be just a little bit safer without significant external vetting.
[+] drewm1980|6 years ago|reply
I wouldn't assume the commenters are young, but if they are, attracting new blood to kernel development is a good thing, right? Especially if the new blood is prevented by the language from introducing memory errors :)
[+] danellis|6 years ago|reply
> but come on, who does that?

Yesterday Go, today Rust.

[+] ajxs|6 years ago|reply
If I recall correctly, Torvalds already addressed the hype around this ( totally not new ) idea by pointing out that memory errors really make up only tiny part of the intricacies of building a kernel. This idea predates Rust, and for better or worse will probably outlive it.
[+] _4ziu|6 years ago|reply
He also said that most of the issues with writing kernels have nothing to do with choice of programming language, but instead hardware compatibility. Neither Rust nor anything else could help this.
[+] megous|6 years ago|reply
Yeah, I would not like this becomming required. Now, it's still fairly manageable to keep developing and building my own kernels. You need binutils/gcc for your host platforms, and that's about all. And I have 3 architectures I build for.

If I also need clang and rust for all the platforms, and learn rust, for some questionable benefits... That would make things hard.

[+] josteink|6 years ago|reply
> If I also need clang and rust for all the platforms, and learn rust, for some questionable benefits

I can see the arguments against needing 2 different C-compiler toolchains, not to mention how this may limit target platform-support to the minimum subset supported by both compilers...

But to argue that Rust only provides "questionable benefits" is really not reasonable.

Even the hipster Javascript crowd has discovered that providing more information to a/the compiler (Typescript) almost unconditionally provides higher code-quality and better results.

When will the C-crowd do the same? When will they shed their "I know better than any machine"-like elitism?

[+] vardump|6 years ago|reply
I think we should just evaluate Rust based on its merits. Ignore fanboys and ignore the opposite as well.

In other words, Rust is just another tool. Regard it as one without subjective emotions either way. Be constructive, don't only look for faults, but also ways how to overcome them. But don't close your eyes from them either. Acknowledging weaknesses is the first step to improvement.

Some possible questions and measures to consider below. I'm sure there's a lot more to add on this list.

1) Stability. Other than for development, unstable kernels are a no go. Can possible negative effects be mitigated?

2) Security is often what Rust is expected to bring on the table. So is Rust actually more secure in the environment kernel requires? This could be tested by "clean-room" reimplementing something that is historically known to have many security issues.

3) Are there showstoppers for kernel builds? Interoperability, build performance, architectures unsupported by Rust, etc. If so, could these be mitigated? Conversely, is there something positive Rust could provide?

4) How does it affect runtime performance? Average case. Bloat issues? Any pathologic cases? Any benefits?

5) What other unexpected it brings on the plate? Both benefits and disadvantages. For example, could Rust types also be used to catch errors other than memory related, like invalid states?

6) <Your consideration here or above>

[+] pcwalton|6 years ago|reply
> 2) Security is often what Rust is expected to bring on the table. So is Rust actually more secure in the environment kernel requires? This could be tested by "clean-room" reimplementing something that is historically known to have many security issues.

We actually have an objective experiment to answer this question: the rust-afl trophy case [1]. It is remarkable how different it is from the upstream AFL trophy case, which tests C and C++ code [2]. An enormous fraction of the AFL trophy case uncovered potentially-exploitable memory safety issues; by contrast, very few of the Rust issues were, with most being safe panics.

[1]: https://github.com/rust-fuzz/trophy-case

[2]: http://lcamtuf.coredump.cx/afl/

[+] cryptonector|6 years ago|reply
I don't think (2) needs any additional demonstration. Any experienced C programmer who just reads the Rust book should conclude that Rust >> C.

(3) can be a problem: Rust does not (yet) make it possible to define an ABI for Rust, which means that only by using external representations (C repr) can Rust code interoperate with non-Rust code. However, that's not a very big deal right now.

As to (1), ignoring toolchain stability (which affects C as well), the main concern would be (3) (see above).

Re (4), that's a legitimate question for sure. (My suspicion is that Rust will improve performance in general, mostly due to forcing better (public and internal) API designs on programmers. However, that's just hunch.)

Re (5), benefits. I think mostly it will bring this benefit: cleaner APIs. Obviously that won't apply to Linux's ABI to user-land, since that's not to be broken, but it could benefit the kernel in-tree. Re (5), disadvantages, I think mainly it's the learning curve.

[+] StavrosK|6 years ago|reply
Isn't this exactly what they're doing?
[+] mehrdadn|6 years ago|reply
> 6) <Your consideration here or above>

"Would C++ fare better or worse than Rust on these fronts?" is the question I would have.

[+] lone_haxx0r|6 years ago|reply
<Your consideration here or above>

I guess that as more languages are added to a project, it gets harder and harder to understand and develop. You need to spend time learning yet another tool to be comfortable with inspecting the code and understanding the inner workings of it.

[+] gmueckl|6 years ago|reply
In this vain, what about other options like beterC (a very capable runtime-less D subset)? What about allowing Ada into the kernel? The fact they are focussing their evaluation on rust only is already an indication that they are playing the fanboy game to some extent. There are other languages out there that improve over C and can be used for systems programming.
[+] geofft|6 years ago|reply
> 1) Stability. Other than for development, unstable kernels are a no go. Can possible negative effects be mitigated?

If by "stability" you mean "crash-proof-ness," I think there's no particular inherent reason Rust code is going to be more crash-prone than C, especially since basically the whole purpose of the language is increased stability. One notable shortcoming is that Rust doesn't currently have a fallible allocations API nor widespread support in common libraries for using it, so under memory pressure, if kmalloc fails, your only choice is to panic (Rust panic, i.e., unwind, maybe BUG() and kill the current thread). See https://github.com/fishinabarrel/linux-kernel-module-rust/is... for some discussion.

If by "stability" you mean interface stability, the Rust project has made great progress in the last year or two at stabilizing everything needed to write code that doesn't use the full standard library / link to a libc that can open files etc. See e.g. https://github.com/fishinabarrel/linux-kernel-module-rust/is... .

> 2) Security is often what Rust is expected to bring on the table. So is Rust actually more secure in the environment kernel requires? This could be tested by "clean-room" reimplementing something that is historically known to have many security issues.

Agree that in practice we're going to need to test this. But all of Rust's safety features (type system that handles null pointers, borrow checker, bounds-checked arrays, safe iterators so you don't need to bounds-check in the first place, etc.) work fine in kernelspace.

> 3) Are there showstoppers for kernel builds? Interoperability, build performance, architectures unsupported by Rust, etc. If so, could these be mitigated?

Build performance is a bit slow, but it's not as bad as userspace Rust because you inherently can't use that many crates and you generally don't want to be linking third-party code anyway - everything should be in the kernel tree.

For architecture support see https://github.com/fishinabarrel/linux-kernel-module-rust/is... . Notably, all the architectures that have kernels by the major distros (I checked RHEL, Fedora, Debian, Ubuntu, SUSE, Android, Oracle, and Arch) should work.

One challenge for interoperability is that most kernels in the real world are built with GCC, and rustc itself emits code using LLVM. The most common way of binding C code is using rust-bindgen, which uses libclang to parse C headers; even if you're not using bindgen, I believe you're still using LLVM's idea of C layout with #[repr(C)] structs and extern "C" functions. It's possible that kernels are built with particular GCC -m options that change the ABI (e.g., regparm) or GCC plugins (e.g., randstruct); if those aren't supported in compatible ways by LLVM / clang, then it's hard to write modules that load into an existing kernel. But, of course, if the question is to build new kernels with components in Rust, one workable restriction is to say that the C parts need to be built with Clang. There is good support for building the Linux kernel with Clang, and there are production Android models with Clang-built kernels.

> 4) How does it affect runtime performance? Average case. Bloat issues? Any pathologic cases? Any benefits?

There's a team that did some investigation on a prototype, and found that runtime performance was comparable, but binary size wasn't too great: https://mssun.me/assets/ares19securing.pdf (The prototype driver uses lots of unsafe code, but it's a good proof of concept for what ought to be achievable.)

I think we can claw back binary size with some focused work.

> 5) What other unexpected it brings on the plate? Both benefits and disadvantages. For example, could Rust types also be used to catch errors other than memory related, like invalid states?

One thing I'm very curious about is whether you can use a battle-tested third-party ASN.1 implementation (for example) instead of writing your own ASN.1 implementation in the kernel. (In fact the kernel has multiple ASN.1 implementations!)

Another useful thing is to use Rust's linear(ish) type system to prevent TOCTTOU bugs when checking userspace pointers, by making it very explicit when you're dereferencing the same address more than once.

A little closer to memory safety: Rust's Send and Sync traits make it easy to ensure you're not unsafely using data across threads (i.e., you're forced to pay attention to shared data and are unlikely to get into a big-kernel-lock situation), and you can use the typesystem to get a better and safer interface to things like RCU pointers. RCU requires that you be in a read-side critical section to read pointers; there's no concept of locking a specific pointer, being in one lets you read any RCU-protected pointer (as long as it's of the same RCU flavor, and confusing RCU flavors did lead to a use-after-free vulnerability recently!). In Rust it's pretty easy to have a Guard object on the stack that uses RAII to enter/exit the critical section, and ensure that you pass a reference to your Guard to any dereference of an RCU pointer type.

[+] amelius|6 years ago|reply
Rust is still a research language. And using more than one language for a kernel/drivers, is that really a good idea? Anyone trying to fix/debug things in the kernel now has to learn Rust.
[+] acd|6 years ago|reply
Could we have universal standardized hardware interfaces for basic functionality of hardware devices? For example at least get the same programming interface for basic functionality of Wifi chips. Then one could have more accelerated drivers which makes use of all the specific hardware features of chips.

Network chips has started to offer a common programming api as far as I understand Switch Abstraction Interface (SAI). Could one do this for more different than network hardware classes?

[+] nindalf|6 years ago|reply
Existing languages in the Linux kernel - https://www.openhub.net/p/linux/analyses/latest/languages_su...

Unsurprisingly it's mostly C (95.6%) with a bit of C++ (2%) and Assembly (1.6%) and tiny bits of make (0.2%), shell scripts (0.3%), Python (0.1% and Perl (0.2%). I'd guess it'd be at least half a decade before Rust cracks 1% here.

[+] cesarb|6 years ago|reply
> with a bit of C++ (2%)

That sounded strange to me, since the dislike of C++ within the Linux kernel is well-known, so I took a quick look. All uses I found were on user space tools, not in the kernel itself, and all of them (except for a "check if this compiles" test file) are in C++ because they have to call into libraries with a C++-only API (LLVM and Qt).

[+] labawi|6 years ago|reply
AFAIK kernel itself is only coded in C and assembly. Shell, python, perl and make are for building and tools.

Linux kernel does not support c++ (its exceptions, RTTI etc). Only c++ I found was related to perf tool, 6 of 7 cpp files had test in their name.

[+] w8rbt|6 years ago|reply
I think setuid executables would be a good place for rust or go, but I have reservations about the kernel itself.
[+] jjtheblunt|6 years ago|reply
Genuine question: who restricts the sourcecode language in which a kernel driver is written?

I'm thinking machine opcodes in a .o file, to be linked wherever, are only of a concern if they're doing function call linkages (and maybe kernel space vs user space transition bookkeeping) improperly?

[+] zelly|6 years ago|reply
C++ written with -pedantic -Wall, smart pointers, and clang static analysis tools, ASan, valgrind, etc. enabled is just as safe as Rust. Change my mind.
[+] mlindner|6 years ago|reply
Valgrind is runtime... You're not going to catch a double free that you don't hit on the common path.

Not to mention race conditions in memory accesses.

[+] jjtheblunt|6 years ago|reply
Does C++ do any setup of signal handling, differently than Rust (which, I am guessing, doesn't)?
[+] bvinc|6 years ago|reply
Are smart pointers thread safe?
[+] stef-13013|6 years ago|reply
Did you talk about that to Linus :):) ?
[+] non-entity|6 years ago|reply
I have Rust on a long backlist of things to check out, but the idea of writing kernel drivers in something other than C is interesting to say the least.
[+] known|6 years ago|reply
Give it as an exercise to college grads
[+] 693471|6 years ago|reply

[deleted]

[+] zelly|6 years ago|reply
in 2025 npm will be a dependency for your kernel build
[+] jstewartmobile|6 years ago|reply
This is running MO for comp sci: adding loads of complexity for a minor gain.

Compile-time borrow-checking works for self-contained applications--the compiler has a complete picture of what's going on. Move that model inside kernel space--where blobs are being mutated across separately compiled modules, different chipsets (CPU/DMA/GPU), sometimes even in parallel--might as well wrap the whole thing in a big `unsafe` block.

Somebody's going to say "Oh, you're exaggerating. It's not that bad in Redox." Redox doesn't have to integrate with 28 years of kernel written in C.

[+] roca|6 years ago|reply
Rust borrow-checking doesn't depend on a closed-world assumption. Federico converted librsvg to Rust and it worked fine.