The rabbit hole of unsafe Rust bugs
notgull.net
notgull.net
Sure, it does some risky pointer math, but that’s considered safe in Rust.
I wouldn't fully agree to that, from The Rust Programming Language book [1]: Much of Rust’s safety comes from compile-time checks, but raw pointers don’t have such guarantees, and are unsafe to use.
[1] https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/sh... strict::with_metadata_of(new_ptr, self.shared.as_ptr() as *mut T)
I always thought pointer math was considered safe because math is safe. Which is fair enough, as adds and subtractions can't do much beyond overflow. What is unsafe is accessing the memory location using the result of that math, as the result could point anywhere.They don't do that (access memory using the raw pointer) here. But if anything what does happen is worse: raw pointer that is considered unsafe to use is cast back to a typed pointer that is considered safe to use, and returned to the unsuspecting caller.
I'm scratching my head on why strict::with_metadata_of() isn't marked as unsafe. In the example the standard library documentation gives https://doc.rust-lang.org/std/primitive.pointer.html#method.... it is wrapped in unsafe{}.
Now, that's apparent the point of the article isn't so clear. The point looks to revolve around this line:
> Eagle-eyed readers might have noticed that this code has no unsafe. Not even a line.
That statement is not really true, as the return type (*T) implies the method is unsafe.
Oh well, it was an interesting bug hunt story.
I thought the whole point of safe code was that any bugs in safe code cannot cause unsound behaivor, so it's on the unsafe code to maintain soundness even if it's used incorrectly by safe code?
For example, in WGPU I get the surface of an OS window. This requires an unsafe function call. The invariant I must uphold is to not drop the window before the surface. This requires writing my safe code a certain way. And indeed, a malicious actor could change only safe code and cause UB by dropping the window (I believe this would cause a double free when the surface is dropped). However, the struct holding the window and surface presents a safe API to users of the struct, and no code outside the struct can cause UB.
Famous example is:
impl Vec {
pub unsafe fn set_capacity(&mut self, capacity: usize) {
self.capacity = capacity; // why is that unsafe?
}
}
What OP possibly did was invalidate his invariants in safe code.Of course, if there's one bug in unsafe code, then all bets are off, but that's still a bug in the unsafe code.
In code I'd consider high-quality, there'd be a comment like this [1]:
// SAFETY: inner is only mutated by foo, bar, and baz, all of which
// ensure the pointer is valid.
[0]: https://doc.rust-lang.org/std/primitive.pointer.html#method....[1]: https://std-dev-guide.rust-lang.org/policy/safety-comments.h...
If you're hoping to enforce across all called functions, it's likely to be unworkable since a lot of stdlib ends up calling unsafe code.
#![forbid(unsafe_code)]
Then it can't be `#[allow()]`ed.There's also things like `cargo gieger` that will tell you how much unsafe code your dependencies have.
You'll never be able to truly escape it, just mostly. -- One interesting suggestion by matthieum on Reddit was to make previously safe fn unsafe.
There would be many examples when code is running at lower levels with no OS abstraction