4 ms·
My whole comment here is that safe code has to care about how it uses data from unsafe code. That's pretty evident from my example and from reading from_raw_par
by Capricorn2481 11mo ago
My whole comment here is that safe code has to care about how it uses data from unsafe code. That's pretty evident from my example and from reading from_raw_parts. How would you fix this by only editing the code inside the unsafe block? Because you said the problem is the lifetime. That's outside of the unsafe block.
> The unsafe code is not fulfilling the contract of "unsafe", which requires the coder to uphold the memory safety rules as seen from outside
It's the exact opposite. From the Rust docs. [1]
> By calling an unsafe function within an unsafe block, we’re saying that we’ve read this function’s documentation and we take responsibility for upholding the function’s contracts
The caller here is what is violating the contract. It's not the unsafe code's job to do that. How could it? It's just making a slice, it doesn't know what from. It can't guarantee a slice it makes is ANY kind of lifetime. If it could, it wouldn't require unsafe.
We don't have to use a static lifetime to have this bug:
// still broken
// basically says it has SOME kind of lifetime
fn make_slice<'a>(ptr: *const i32, len: usize) -> &'a [i32] {
Lifetime annotations are just annotations for the compiler, but they mean very little when using unsafe. I wrote a bad lifetime to illustrate that Rust doesn't care. Normally, the compiler would make sure your lifetimes make sense, but not with unsafe. Because it's the calling code that has to care.
Yes, if you change the function signature and require it to receive the vec so you can see the lifetime, you could technically fix it from this function. That's the only way to get the right lifetime.
fn make_slice<'a>(ptr: *const i32, len: usize, _lifetime: &'a Vec<i32>) -> &'a [i32] {
So yes, as long as you change your function so much you can basically remove unsafe, you can fix it from the function.
But you can't do this in scenarios where you'd actually want unsafe, like FFI, custom data structures, etc. You don't have the same lifetime information available to you.
[1] https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html#calling-an-unsafe-function-or-method https://doc.rust-lang.org/book/ch20-01-unsafe-rust.html#call...
- lunar_mycroft 11mo ago> How would you fix this by only editing the code inside the unsafe block? In this case, the solution is to mark make_slice as unsafe. This should make sense if you think about it for a second, because it's a thin wrapper around an unsafe function. If it were possible to make a function which did what std::slice::from_raw_parts does safe, it wouldn't be marked as unsafe. > It's the exact opposite. From the Rust docs. The docs you quote say the exact opposite of what you claim. Where do you do call the unsafe function in an unsafe block? In make_slice, not main. > So yes, as long as you change your function so much you can basically remove unsafe, you can fix it from the function. The actual use case for std::slice::from_raw_parts is for when you control the pointer and length and thus can guarantee that they're valid (specifically, by encapsulating them and making sure no safe functions can get them into an invalid state). For example, std::Vec::as_slice uses it [1] to get a slice from the pointer to it's managed buffer and it's stored length. > FFI There are two possible ways FFI could cause memory safety violations despite being called from safe code: - The safe wrapper makes incorrect assumptions about the contract of the functions it's calling (e.g. maybe it assumes that a c function returns a pointer which it's the caller's responsibility to free, but is actually freed by the c code). In this case, the bug is in the unsafe rust. - The foreign code violates it's contract (e.g. same as above, but the documentation of the c code claims otherwise). In that case, the bug is in the foreign code. Either way, the memory safety bug is not in safe rust. [1] https://github.com/rust-lang/rust/blob/7934bbdf84a6b9a30297caf4f4f38286dedf876a/library/alloc/src/vec/mod.rs#L1585 https://github.com/rust-lang/rust/blob/7934bbdf84a6b9a30297c...
- Capricorn2481 11mo ago> The actual use case for std::slice::from_raw_parts is for when you control the pointer and length and thus can guarantee that they're valid (specifically, by encapsulating them and making sure no safe functions can get them into an invalid state) Right...so you fix it from the safe code, because the safe code is in charge of using from_raw_parts correctly. > Either way, the memory safety bug is not in safe rust This might just be a difference of semantics between us, but I really don't see how you can arrive at that conclusion. Just remove the function entirely and let's use from_raw_parts directly. The bug is literally not in unsafe, it's when we drop the vec after making a slice from it. To fix this, we would not change anything in unsafe. let vec: Vec<i32> = vec![1, 2, 3, 4, 5]; let ptr = vec.as_ptr(); let len = vec.len(); let slice_ref = unsafe {slice::from_raw_parts(ptr, len)}; drop(vec); println!("{:?}", slice_ref);
- Dylan16807 11mo ago> Right...so you fix it from the safe code, because the safe code is in charge of using from_raw_parts correctly. Safe code cannot corrupt the Vec variables to make Vec do something incorrect. > To fix this, we would not change anything in unsafe. You're supposed to. Some coders might implicitly count the rest of the function as trusted code that's part of enforcing the invariants of the unsafe code. So there's an implied "unsafe" block that fills up the rest of the function, as far as responsibility goes. I won't argue one way or the other on that philosophy, but if anything outside the function with the unsafe block is part of enforcing invariants, then that is clearly wrong. If you subscribe to that philosophy, then you could say the drop is the problem in this single-function case. But in the original the bug is still inside make_slice, because make_slice is unleashing a fragile and uncontrolled slice into the safe world of main. (There might be some more subtleties when it comes to class API boundaries but I don't want to dig through that right now, it's not relevant to the important part of this conversation.)
- Capricorn2481 11mo ago>> To fix this, we would not change anything in unsafe. > You're supposed to. To me, it just sounds like we're disagreeing on definitions. You can view it as unsafe corrupting the vec, I don't disagree with that, but it's the way the safe code coordinated the unsafe code that caused that problem. I'm happy to be shown a different way. Would you kindly show me how you would fix this in the unsafe block only? > Some coders might implicitly count the rest of the function as trusted code that's part of enforcing the invariants of the unsafe code. So there's an implied "unsafe" that fills up the rest of the function, as far as responsibility goes That's essentially what I'm arguing. > but if anything outside the function with the unsafe block is part of enforcing invariants, then that is clearly wrong What is clearly wrong? I'm not saying none of it is wrong, I'm just saying unsafe allows you to make bugs outside of it. And yes, it can happen at arbitrary depth, not just in the function containing unsafe code.
- Dylan16807 11mo ago> The caller here is what is violating the contract. "The caller" of the unsafe function is the unsafe block in make_slice. And yes it's violating the contract. > How would you fix this by only editing the code inside the unsafe block? Because you said the problem is the lifetime. That's outside of the unsafe block. If it's locked into returning a static lifetime, then the only fix is to return a slice into a static allocation. Is this the functionality you wanted? Probably not. But an unsafe block having an awkward API isn't an excuse to violate safety. Sometimes the only answer is to say no. Calling from_raw_parts this way is a clear bug. > How could it? It's just making a slice, it doesn't know what from. It could know more if you added more to the unsafe block outside the call to from_raw_parts. > It can't guarantee a slice it makes is ANY kind of lifetime. If it could, it wouldn't require unsafe. > as long as you change your function so much you can basically remove unsafe, you can fix it from the function. from_raw_parts exists for situations where the coder can guarantee that the lifetime is valid (no matter what safe code does), but the compiler can't guarantee it (because it's not smart enough). It's not there for situations where you make the safe code promise to behave. It does have a lot of uses, in places that do need unsafe. You just happened to make an example that doesn't need it.
- Capricorn2481 11mo agoIt's not a bug until main uses it incorrectly. The lifetime is outside of the unsafe block. I can change it from static and we still have the same bug.
- Dylan16807 11mo ago> It's not a bug until main uses it incorrectly. It's always a bug. The lifetime is wrong. The mere ability to "use it incorrectly" and break memory safety is evidence of a bug. Safe rust in a program without unsafe can't use anything incorrectly in that way. Safe rust in a program with appropriately careful unsafe can't use anything incorrectly in that way. > The lifetime is outside of the unsafe block. The unsafe block uses the types from the surrounding function, so you can't change those types without double checking the unsafe block is still valid. > I can change it from static and we still have the same bug. Change it to something correct and have the same bug? Because your continued examples look like no. If you make the lifetime arbitrary then it's still messed up, and if you get the lifetime off the vec then the bug is fixed.