Language vision regarding safety guarantees
What I mean when I say that "safe code cannot cause UB" is that if there's UB, you can always attribute it to an incorrect unsafe block.
I know that you sometimes end up in a situation where the unsafe block looks like this:
let x = library::foo();
// SAFETY: Library is implemented correctly.
unsafe { ub_if_library_is_incorrect(x) };
If that safety comment turns out to be wrong, well shucks. The author of that unsafe block took a risk, and it didn't work out. So this is a scenario where:
- There is UB.
- The fix is to change the safe code, and not the unsafe block.
Given this, was the unsafe block wrong? Well, there is certainly one way in which it's wrong: It had a safety comment saying "library is implemented correctly" and, well, it wasn't implemented correctly, so it had an incorrect safety comment. That makes the unsafe block wrong.
Of course, there's also another way in which it's not wrong: When we fixed the bug, the unsafe code did not change. If it did not change and is now correct, it must have been correct all along?
So given that, I agree the situation is not 100% clear.
However, here's what I think is the right way to look at it: UB is often caused by several things going wrong at the same time. After all, we could fix the UB by changing the unsafe block too! Delete the unsafe { ub_if_library_is_incorrect(x) } line of code, and voilà, no UB. So I guess the UB was caused by that unsafe block after all!? Sure, the bug in library remains, but the UB is gone.
Enough about UB, let's talk about vulnerabilities.
CVE-2024-27308 is an interesting example. Mio documents a guarantee in its docs, and Tokio relied on it in unsafe code. It turns out that the windows implementation violated this contract in some circumstances.
At the time, it was actually argued to me that the CVE should be filed under Tokio because in mio it's just a bug, and it's Tokio that actually triggered UB as a result. However, my take is that this is Rust idealism. There is no other programming language where you would come to the conclusion that the CVE should be filed under Tokio. This conclusion stems purely from the concept of "unsafe" and how closely it's tied with UB in our community. Filing it under mio makes much more sense because, for instance, in a CVE you list which versions are affected and also the version in which the bug was fixed. Those questions cannot be answered if it's filed under Tokio because the fix was to change mio.
So did mio cause this CVE or did Tokio cause it? Well, on one hand the bug is in mio and the fix was to change mio, so it seems clear that mio is the cause. However, it's not quite that clear. As the CVE says, versions of Tokio prior to v1.30.0 were not actually vulnerable because prior to v1.30.0, Tokio would use the mio token as a key into a map, rather than cast it to/from a pointer. If we had never made this change in Tokio, then I do not think this mio bug would have been filed as a CVE. It would just have been a bug. So while the unsafe code in Tokio did not cause the bug in mio, it did cause the CVE.
Discussion in the ATmosphere