Tracking trust with Rust in the kernel
Ready to give LWN a try? With a subscription to LWN, you can stay current with what is happening in the Linux and free-software community and take advantage of subscriber-only site features. We are pleased to offer you a free trial subscription, no credit card required, so that you can see for yourself. Please, join us!
The Linux kernel has to handle many different sources of data that should not be trusted: user space, network connections, and removable storage, to name a few. The kernel has to remain secure even if one of these sends garbled (or malicious) data. Benno Lossin has been working on an API for kernel Rust code that makes it harder to accidentally make decisions based on data from user space. That work is now on its fourth revision, and Lossin has asked kernel developers to experiment with it and see where problems remain, making this a good time to look at the proposed API.
The core approach, as with so many things in Rust, centers on the type system. Lossin's patch set introduces a new type, Untrusted , that marks data as originating from an untrusted source, and therefore requiring special caution. Trying to access a value wrapped by Untrusted is forbidden by Rust. The type is a "transparent" structure, meaning that it will be laid out in memory exactly like the type that it wraps. An Untrusted is a single byte, for example. The type therefore has no run-time overhead, so it can be used as a kind of marker in the type system for data that has come from user space without being validated. That makes it impossible to pass into functions that expect normal kernel data by accident.
The bulk of Lossin's patch set is documentation for Untrusted , and some utility functions to manipulate untrusted values. There is also special support for common data structures, specifically slices (arrays with a run-time-known length) and vectors (growable arrays on the heap) of untrusted values. When dealing with an existing buffer that should be filled with untrusted data, the documentation recommends writing the interface like this:
pub fn read_from_userspace(buf: &mut [Untrusted])
That function takes a mutable reference to a slice of untrusted bytes, and will fill it with user-space data. That data can later be copied back to user space without having to unwrap it. Trying to actually use the value of an untrusted object within the kernel, however, will cause a compiler error. Lossin recommends that form of API because converting an &mut Untrusted<[u8]> (a mutable reference to an untrusted slice of bytes) into an &mut [Untrusted] (a mutable reference to a slice of individually untrusted bytes) can be done automatically — conversions are inserted where needed by the compiler due to the DerefMut implementation for Untrusted — but converting things the other way around requires an explicit function call. If Rust developers get in the habit of writing APIs involving Untrusted in this way, they'll be less of a hassle to use.
Sometimes, the kernel does need to read user-space data, not merely copy it around. The third patch of the set contains functions to help with that. It introduces a new trait called Validate that encapsulates the logic for validating user-space data before use. For a custom type T , an implementation of Validate contains the logic needed to turn an Untrusted into a plain T . Lossin isn't totally happy with that API, though, and wants to find time to improve it.
Greg Kroah-Hartman, who has been enthusiastic about the idea of marking input from user space in the past, asked for Lossin to add an example of a driver using Untrusted .
ioctl() is the callback that is taking untrusted data from userspace. That's one place we have had more kernel buffer overflows then I can count and ALWAYS needs to be properly verified before anything can be done with the data there.
Lossin hasn't written a complete example driver, but did share a rough sketch of what using the new API to implement a driver ioctl() function might look like. ioctl() is a tricky interface because it takes two parameters: cmd , which specifies which command to run, and arg , the meaning of which depends on cmd . How arg should be validated depends, therefore, on which command user space has sent. Lossin suggested representing this in Rust with an enumeration:
enum MyIoctlArgs { WriteFoo(UserPtr), VerifyBar(UserPtr), // ... }
With some suitable tweaks to the Rust driver API, the generic MiscDevice::ioctl() function could take a structure that bundles cmd and arg together:
pub struct IoctlArgs { pub cmd: u32, pub arg: usize, } fn ioctl( _device: ::Borrowed<'_>, _file: &File, _args: Untrusted, ) -> Result
The Untrusted could then be validated into a normal MyIoctlArgs value. This would not prevent the programmer from botching the validation logic, of course, but it would force the conversion to happen in one specific place, and enforce that it is called. Hopefully, that makes it easier to spot missing checks.
Kroah-Hartman raised the problem of bugs where the kernel "validates" a particular piece of data while it is still accessible to user space, and only then copies it with copy_from_user() . He wondered how this interface could protect against that. The UserPtr types in MyIoctlArgs in his example help there, Lossin explained. These represent pointers to user-space memory; when combined with a length, they can become a UserSlice . The UserSlice::read_all() method being the Rust equivalent of copy_from_user() . Right now, read_all() just reads bytes directly, but if his patch set is accepted, he would want to change it to mark the read bytes as untrusted:
pub fn read_all( self, buf: &mut Untrusted>, flags: Flags ) -> Result
With the types Lossin sketched out, the type system would theoretically enforce the progression from Untrusted , to MyIoctlArgs , to UserPtr , to Untrusted> , to whatever user-defined type is appropriate for the argument of ioctl() . Since he did not provide a full, working example, there are still some questions his response leaves unanswered, but Kroah-Hartman was happy with it. At the time of writing no other comments on the patch set have been made. If the API is adopted in its current form, it will necessitate pervasive changes to the interfaces for Rust drivers, so the patch set seems likely to be a topic of discussion at the upcoming Kangrejos conference about the Rust for Linux project in September.
to post comments