6 ms·
To pre-empt the folks who'll come in here and laugh about how Rust should be preventing memory corruption... I'll just directly quote from the mailing list:
by tekacs 10mo ago
To pre-empt the folks who'll come in here and laugh about how Rust should be preventing memory corruption... I'll just directly quote from the mailing list:
Rust Binder contains the following unsafe operation:
// SAFETY: A `NodeDeath` is never inserted into the death list
// of any node other than its owner, so it is either in this
// death list or in no death list.
unsafe { node_inner.death_list.remove(self) };
This operation is unsafe because when touching the prev/next pointers of
a list element, we have to ensure that no other thread is also touching
them in parallel. If the node is present in the list that `remove` is
called on, then that is fine because we have exclusive access to that
list. If the node is not in any list, then it's also ok. But if it's
present in a different list that may be accessed in parallel, then that
may be a data race on the prev/next pointers.
And unfortunately that is exactly what is happening here. In
Node::release, we:
1. Take the lock.
2. Move all items to a local list on the stack.
3. Drop the lock.
4. Iterate the local list on the stack.
Combined with threads using the unsafe remove method on the original
list, this leads to memory corruption of the prev/next pointers. This
leads to crashes like this one:
- samdoesnothing 10mo agoIf rust is so inflexible that it requires the use of unsafe to solve problems, that's still rust's fault. You have to consider both safe rust behaviour as well as necessary unsafe code.
- woodruffw 10mo agoThis is sort of the exact opposite of reality: the point of safe Rust is that it's safe so long as Rust's invariants are preserved, which all other safe Rust preserves by construction. So you only need to audit unsafe Rust code to ensure the safety of a Rust codebase. (The nuance being that sometimes there's a lot of unsafe Rust, because some domains - like kernel programming - necessitate it. But this is still a better state of affairs than having no code be correct by construction, which is the reality with C.)
- gkbrk 10mo agoWhich domain doesn't necessitate unsafe? Any large Rust project I check has tons of unsafe in its dependency tree.
- treyd 10mo agoAnd that is fine, because those upstream deps can locally ensure that those sections are correct without any risk that some unrelated code might mis-safely use them unsafely. There is an actual rigorous mathematical proof of this. You have no such guarantees in C/C++.
- informa23 10mo ago[flagged]
- dijit 10mo agoThose guys were writing a lot of unsafe rust and bumped into UB. I sound like an apologist, but the Rust team stated that “memory safety is preserved as long as Rusts invariants are”. Feels really clear, people keep missing this point for some reason, almost as if its a gotcha that unsafe rust behaves in the same memory unsafe way as C/C++: when thats exactly the point. Your verification surface is smaller and has a boundary.
- aw1621107 10mo agoI think you're misunderstanding GP. The claim is that the only party responsible for ensuring correctness is the one providing a safe API to unsafe functionality (the upstream dependency in GP's comment). There's no claim that upstream devs are infalliable nor that the consequences of a mistake are necessarily bounded.
- treyd 10mo ago> And a bug in one crate can cause UB in another crate if that other crate is not designed well and correctly. Yes! Failure to uphold invariants of the underlying abstract model in an unsafe block breaks the surrounding code, including other crates! That's exactly consistent with what I said. There's nothing special about the stdlib. Like all software, it can have bugs. What the proof states is that two independently correct blocks of unsafe code cannot, when used together, be incorrect. So the key value there is that you only have to reason about them in isolation, which is not true for C.
- kstrauser 10mo agoWhat's the alternative that preserves safe-by-default while still allowing unlimited flexibility to accidentally break things? I mean, Rust allows inline assembly because there are situations where you absolutely must execute specific opcodes, but darned if I want that to be the common case.
- thayne 10mo agoThat's like saying that if c is so inflexible it requires the use of inline assembly to solve problems, it's C's fault if inline assembly causes undefined behavior.
- bigstrat2003 10mo ago> If rust is so inflexible that it requires the use of unsafe to solve problems... Thankfully, it doesn't. There are very few situations which require unsafe code, though a kernel is going to run into a lot of those by virtue of what it does. But the vast majority of the time, you can write Rust programs without ever once reaching for unsafe.
- ncruces 10mo agoIt's not a kernel. It's the, admittedly very complicated, concurrent double linked list. I say that with no irony.
- lynndotpy 10mo agoYes. When writing unsafe, you have to assume you can never trust anything coming from safe rust. But you are also provided far fewer rakes to step on when writing unsafe, and you (ideally) are writing far fewer lines of unsafe code in a Rust project than you would for equivalent C. Rust is written in Rust, and we still want to be able to e.g. call C code from Rust. (It used to be the case that external C code was not always marked unsafe, but this was fixed recently).
- quotemstr 10mo agosamdoesnothing is making a legitimate point about needing to consider prevalence of unsafe inna Rust program. That he's being downvoted to hell is everything wrong with HN.
- Phelinofist 10mo agoI know nothing about Rust. But why is unsafe needed? Kinda sounds a lock would make this safe?
- aw1621107 10mo ago> I know nothing about Rust. But why is unsafe needed? The short of it is that for fundamental computer science reasons the ability to always reject unsafe programs comes at the cost of sometimes being unable to verify that an actually-safe program is safe. You can deal with this either by accepting this tradeoff as it is and accepting that some actually-safe programs will be impossible to write, or you can add an escape hatch that the compiler is unable to check but allows you to write those unverifiable programs. Rust chose the latter approach. > Kinda sounds a lock would make this safe? There was a lock, but it looks like it didn't cover everything it needed to.
- dijit 10mo agoI think you missed the parents point. We all universally acknowledge the need for the unsafe{} keyword in general; what the parent is saying is: given the constraint of a lock, could this code not have obviated the need for an unsafe block entirely. Thus rendering the memory safety-issue impossible.
- Phelinofist 10mo agoYes, that is what I meant - thanks for actually expressing my thoughts better than me.
- aw1621107 10mo agoAh, I see that interpretation now that you spelled it out for me. Here's what `List::remove` says on its safety requirements [0]: /// Removes the provided item from this list and returns it. /// /// This returns `None` if the item is not in the list. (Note that by the safety requirements, /// this means that the item is not in any list.) /// /// # Safety /// /// `item` must not be in a different linked list (with the same id). pub unsafe fn remove(&mut self, item: &T) -> Option<ListArc<T, ID>> { At least if I'm understanding things correctly, I don't think that that invariant is something that locks can protect in general. I can't say I'm familiar enough with the code to say whether some other code organization would have eliminated the need for the unsafe block in this specific case. [0]: https://github.com/torvalds/linux/blob/3e0ae02ba831da2b707905f4e602e43f8507b8cc/rust/kernel/list.rs#L582 https://github.com/torvalds/linux/blob/3e0ae02ba831da2b70790...
- themafia 10mo agoSo the prediction that incautious and unverified unsafe {} blocks would cause CVEs seems entirely accurate.
- K0nserv 10mo agoIsn’t it obvious that the primary source of CVEs in Rust programs would be the portions of the program where the human is charge of correctness instead of the compiler? The relevant question is whether it results in fewer and less severe CVEs than code written in C. So far the answer seems to be a resounding yes
- Hemospectrum 10mo agoIt is not obvious to those who refuse to understand, and who preemptively reject case studies on the grounds that the numbers are surely fabricated.
- woodruffw 10mo ago"Cause" seems unsubstantiated: I think to justify "cause," we'd need strong evidence that the equivalent bug (or worse) wouldn't have happened in C. Or another way to put it: clearly this is bad, and unsafe blocks deserve significant scrutiny. But it's unclear how this would have been made better by the code being entirely unsafe, rather than a particular source of unsafety being incorrect.
- ganelonhb 10mo agoThe definition of cause is quite clear. “Rust” is obviously not the cause, but it did fail to be the solution, here. You can’t avoid that.
- woodruffw 10mo agoExcept that it didn't fail to be the solution: the bug is localized to an explicit escape hatch in Rust's safety rules, rather than being a latent property of the system. (I think the underlying philosophical disagreement here is this: I think software is always going to have bugs, and that Rust can't - and doesn't promise - to perfectly eliminate them. Instead, what Rust does promise - and deliver on - is that the entire class of memory safety bugs can be eliminated by construction in safe Rust, and localized when present to errors in unsafe Rust. Insofar as that's the promise, Rust has delivered here.)
- torginus 10mo agoSorry, but this is like saying 'when I am not wrong, I am right 100% of the time'. The devs didn't write unsafe Rust to experience the thrills of living dangerously, they wrote it because the primitives were impossible to express in safe Rust. If I were to write a program in C++ that has a thread-safe doubly linked list in it, I'd be able to bet on that linked list will have safety bugs, not because C++ is an unsafe language, but because multi-threading is hard. In fact, I believe most memory safety errors today occur in the presence of multi-threading. Rust doesn't offer me any way of making sure my code is safe in this case, I have to do the due diligence of trying my best and still accept that bugs might happen because this is a hard problem. The difference between Rust and C++ in this case, is that the bad parts of Rust are cordoned off with glowing red lines, while the bad parts of C++ are not. This might help me in minimizing the attack surface in the future, but I suspect Rust's practical benefits will end up less impactful than advertised, even when the language is full realized and at its best, because most memory safety issues occur in code that cannot be expressed in safe Rust and doing it in a safe Rust way is not feasible for some technical reason.