4 ms·
> 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
by 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> Would you kindly show me how you would fix this in the unsafe block only? I'd probably hold a reference to the vector or something. > That's essentially what I'm arguing. The thing is it's only the safe code in the same function as the unsafe block that you could call responsible in any reasonable sense. In the original version the main() code cannot be blamed for dropping a vector 'early', because it didn't invoke this scheme that requires the vector to be kept alive, make_slice did. It was make_slice's responsibility to guarantee the underlying allocation stays alive, and it didn't do that. > 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. If you're putting responsibility on code in the same function, you're not really treating it as safe code. There's an unsafe{} around the entire function body that you should have written if you were going for maximum clarity. If the depth goes any higher, you screwed up. It should be impossible for safe code outside the function to use your code and cause a memory error. If it's not impossible then that's a bug in the unsafe blocks (or the function with the unsafe blocks).
- Capricorn2481 11mo ago> I'd probably hold a reference to the vector or something In a way that only changes the unsafe block? > make_slice's responsibility to guarantee the underlying allocation stays alive, and it didn't do that We çan talk about the simpler code example. How can my unsafe block make sure an allocation stays alive past the unsafe block itself? > If you're putting responsibility on code in the same function, you're not really treating it as safe code. There's an unsafe{} around the entire function body that you should have written if you were going for maximum clarity I'm not talking about what should've been done, I'm saying this is something to be aware of on projects with multiple people who may not catch something like this. People think bugs cannot happen in safe code. I just showed it happening. The bug was not in unsafe, or in the drop, it was in println.
- Dylan16807 11mo ago> How can my unsafe block make sure an allocation stays alive past the unsafe block itself? Put it in another data structure or something? Listen, if you can't make the invariant work, then you need to change the function. An unfixable unsafe is not an excuse to allow errors > I'm saying this is something to be aware of on projects with multiple people who may not catch something like this. It's good to be aware but put the blame in the right place. It's the unsafe code that's actually at fault. If you are seeing corruption, look at the unsafe code first with an adversarial mindset. > The bug was not in unsafe, or in the drop, it was in println. N. O. Any unsafe code that outsources invariant enforcement that affects memory safety to safe code has bugs. There's wiggle room on "outsources" but that's the only wiggle room.
- lunar_mycroft 11mo ago> Right...so you fix it from the safe code, because the safe code is in charge of using from_raw_parts correctly. No, you fix it from the unsafe code (just not your unsafe code, since the API you're trying to expose is not possible to make safe). You encapsulate the arguments to from_raw_parts, provide a safe interface, and only manipulate the encapsulated data inside other unsafe blocks. > 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. I think you're correct that semantics are at the core of our disagreement. Specifically, there are three different possible ways to answer the question of where a bug is: 1. Where the bug manifests a problem (produces an incorrect value, segfaults, etc). 2. Where the bug is directly caused. 3. Where the incorrect code is. In the code you provided, 1 happens at the println, 2 happens at the drop, but 3 happens in the unsafe block. All your safe code is correct according to rust's memory safety rules rules (which is why it would pass the borrow checker), the problem is that by using an unsafe block you promised to uphold those rules yourself, and have failed to do so.