Does Rust in fact prevent most of these at compile time? Any that it does not?
Does Rust in fact prevent most of these at compile time? Any that it does not?
Yes, it would be very weird to manage to do this in rust. It would require screwing up badly in unsafe code. I guess the most likely way to fuck up unsafe code in this way would be to screw up a custom datastructure like a custom reference counted pointer (but the obvious solution is to just use the standard ones).
- 8x NULL pointer dereference
Yes, rust tells you when pointers might be Null (which is represented by saying the pointer is Option<PointerType> instead of just PointerType), and doesn't let you use the pointer in a way that implicitly assumes it isn't null.
- 6x general protection
These are generally memory errors, and are certainly undefined behavior, so yes.
- 6x slab-out-of-bounds access
"Yes" with a small caveat, depending on how you are using the slab it is likely that Rust doesn't force you to explicitly think about the out of bounds case and turns just silently turns the security vulnerability into a runtime error, which might be handled farther up the stack or might cause the kernel to halt.
- 14x use-after-free access
Yes, like double frees. The range of possible fuckups in unsafe code that cause this is probably slightly larger than the equivalent range for double frees.
USB registers might be in DMA buffers or another memory location that might get mapped/unmapped at any point. Not everything fits a simplistic linear memory model.
So it won't be perfect, but you can add safety versus what you get in C. You can even add safety versus what you'd get in C++ because of the lifetimes you can associate.
I.e. say I have a `x: Rc<[u8]>`, that is a ref counted pointer to some memory, and a length of that memory. I can do `let y: &[u8] = &x;`. `y` is now a not-ref counted pointer to the same memory (with the same length), that's guaranteed to be dropped before `x` is so the memory won't be freed from under it. I can also do `let z: &u8 = &x[5]`. `z` is now a pointer to a byte in `x`. Like `y` it's not ref counted and the compiler will force us to drop it before we drop `x`.
You can make a whole allocator in this fashion (people have, even in the standard library I believe). If you get really clever you can probably even make an allocator in this fashion where the allocator doesn't use any unsafe code, you can easily make one where the users of the allocator don't need any unsafe code.
The rust borrow checker works on the principle of ensuring that if you have a unique pointer (&mut) to something, nothing else can access it. If you have a shared pointer (&) to something, nothing else is mutating it except where internal mutability is explicitly marked (UnsafeCell, and abstractions using UnsafeCell such as Cell, RefCell, Mutex, RwLock, and so on).
To mutate a page table entry you would need an &mut reference to it, to access a page you would need an & reference to the page table entry (from the &PageTableEntry you would get a &[u8] pointing to the data which the borrow checker would guarantee you drop before you drop the &PageTableEntry before anything mutates the PageTableEntry).
Shows you how you might implement paging, and how much unsafe you need to do so.
I think this is a fallacy...The majority of C code doesn't manipulate pointers either. The point is, the moment that you have _any_ unsafe code (C or Rust), it's a question of time before you will have some bugs, especially if you have a very large number of people working on the same code base...you may be extra careful, maybe the next guy is not...Rust is not going to magically solve these problems for unsafe code...
Rust will not make your code magically bugfree (this is basically impossible by definition), but it will undoubtedly reduce the number of bugs in a very significant way and nudge you towards better code.
But it's not just pointers. C is unsafe at every turn. Assignments can give undefined behaviour, as can the arithmetic operators, as can varargs... the list goes on.
int i;
int j = i; // undefined behavior
int m = 0;
int n = 42 / m; // undefined behavior
int x = INT_MIN;
int y = -1;
int z = x / y; // undefined behavior (assuming two's complement)
printf("%d"); // undefined behaviour
> Rust is not going to magically solve these problems for unsafe code...It doesn't need to. By providing a language where only a tiny fraction of a well-designed codebase needs to use the unsafe features, the number of safety-related bugs can be vastly reduced.
Rust does not aim for perfection. If you want that, your best bet is formal methods.
No, in C a NULL pointer dereference is not a crash, it's undefined behavior (which yes, can manifest as a crash), which is much more unpredictable.
For anyone still believing a NULL dereference (or any of the other UB that is in the C spec) is just a segfault, "What Every C Programmer Should Know About Undefined Behavior" is still required reading:
http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
Here's how triggering UB ends up as a vulnerability:
To be fair, dereferencing NULL ended up as a vulnerability due to
1. the optimiser removing the subsequent NULL check, and
2. the zero page being mapped.
While 1. may happen, depending on how sophisticated the compiler is, 2. was already a security issue and is 'almost always' not the case.
Usually the best thing any C/C++ developer can do is crank the warn levels as high as they can go. Then set the project to error out on warn. A good code smell for a project is when you see one that has turned off warnings. Usually the reason is 'too noisy'. I always tell my junior devs the same thing 'the compiler is trying to tell you something all you have to do is listen'.
The same for "NULL pointer dereference"; the Rust equivalent (without using raw pointers, which require "unsafe") is calling .unwrap() on an Option<T> which has a None, which once again is not prevented at compile time, only converted into a panic at run time.
Edit: note, however, that idiomatic Rust can help prevent these bugs, by using iterators instead of indexed access, and match/if-let statements instead of unwrap/expect.
Drivers by their very nature require a lot of unsafe pointer passing...Having worked on a lot of embedded Linux driver code, I'm not convinced that you could do without a ton of unsafe code...which basically negates all of Rust's safety guarantees...The current architecture just doesn't lend itself well to Rust (in my opinion), so you would have to basically rewrite very large parts of the plumbing, which by its very nature would introduce a ton of new bugs...
Drivers are a fundamentally unsafe concept, like an ffi call, you're talking to hardware the compiler doesn't understand and assuming it's going to do what you want. So there will of course be some unsafe. In a well implemented driver it will also be very limited in scope and relatively easy to check.
I can't vouch for the code quality (I just don't know how good it is, I had no hand in writing it, and it's not used in a production system), but I believe you can find a usb driver implementation written in rust here: https://gitlab.redox-os.org/redox-os/drivers/-/tree/master/x...
I'm sure that is what the C developer thought as well...I'm not trying to be snarky, but that same arguments that are made against C code holds equally true for unsafe code...
I don't think it is a reasonable position to suggest that unsafe Rust code is somehow safer than C code...
You can limit what comes in from safe rust.
Unsafe rust still has more compiler checks than C. Unsafe rust is not as permissive as C. Its rust still, with certain things permitted.
Its unsafe{} not nosafe{}
The idea is that you build an abstraction and wrap away the unsafety. That abstraction has to be built defensively, which is enabled by the type system enforcing very powerful invariants across the program. You can safely encode the way your structures may or may not be used across threads, enforce exclusive vs shared access, control mutability.
Unsafe is not a "do whatever" card, you use it with the rest of the language as one more tool that helps build safe programs.
Modern toolchains can effectively warn about nemory/pointer issues. I cant wait for a C++2x proposal to add Rust like memory semantics to the language - primarily to shut Rust fanboys up. Also, Rust does not warn about threading race conditions, you’d need a reference capabilities like language (Pony without ORCA) for that.
Some are aware that many other langs are memory safe and yet that's not a reason enough to rewrite a kernel.