Rust and static safety
Rust and static safety
Posted Jul 22, 2023 1:32 UTC (Sat) by geofft (subscriber, #59789)Parent article: Exceptions in BPF
Well... kind of. There are indeed a large number of things that cause a Rust panic (which, for kernelspace Rust code, turns into an oops, not a kernel panic), such as indexing an array out of bounds. But there are also a large number of things that are verified by the Rust compiler, preventing wrong behavior before it can ever execute, and I think that's sort of the selling point of Rust!
A good example is the venerable null pointer. It's not actually a pointer - it's someone using a pointer type to convey there is nothing to point to. In C, you can attempt to "dereference" a null pointer, which is a fundamentally meaningless operation that will lead to incorrect behavior. In Rust, pointer types are defined as non-null, and the standard Optional data type when wrapping a pointer ends up in memory just like a nullable C pointer, but the language prevents you from using it if the value is equal to zero. An Optional type cannot be used directly; you have to use an if or match statement that breaks down the two possibilities, Some actual value or None. (Or you can call a function that does so - such as the standard .unwrap() function which will generate a Rust panic if it's None.)
In other words, at compile time, a Rust program can be verified as never dereferencing a null reference.
This can be generalized in a few ways. Rust also ensures that all references are to valid data, which is what the feared "borrow checker" does: if the compiler can't be convinced that the pointed-to data is still around when you're using the pointer, it will fail to compile. You can also imagine data types that have more than just null as special cases, such as the kernel's ERR_PTR scheme, where small negative values are actually errnos. A function that returns a char * might actually return (char *)-ENOMEM, aka ERR_PTR(-ENOMEM), and expect callers to check IS_ERR on the pointer before using it, with bad consequences if they forget. In Rust this is better defined as a data type that can be either a pointer or an error code (and indeed rust/kernel/error.rs defines it this way): you can't misinterpret an error code as a pointer or vice versa. Currently this isn't stored all in one pointer-sized data type the way it is in C, but it will be soon.
The big difference between Rust and BPF in this context is that Rust treats all of these tools as aids to the programmer, which can be bypassed if needed if you're doing something complex, and BPF treats these as hard requirements and simply refuses to let you do complex things. Rust is trying to eliminate common types of mistakes, but it's not intended to be used in a way where the output is more highly privileged than the input. So it is possible (and quite common for interop with C) to "unsafely" produce a reference from somewhere, effectively telling Rust, hey trust me on this one, this is a valid pointer even though I can't prove it to you. BPF, on the other hand, is all about allowing userspace to load programs into the kernel without the security risk of loading a real kernel module. So it can't have any bypass mechanisms. (And so it needs something like the mechanism in this article to say, I can't prove this assumption to you, so you can just evaluate whether it's true at runtime and bail out of executing the program if it isn't.)
Posted Jul 22, 2023 16:19 UTC (Sat)
by randomguy3 (subscriber, #71063)
[Link]
Rust and static safety