-
Notifications
You must be signed in to change notification settings - Fork 13.3k
avoid violating slice::from_raw_parts
safety contract in Vec::extract_if
#141032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I was unable to trigger a miri failure with this. I seems to have something to do with how deeply value validity is checked because miri correctly flags this code: let ptr = std::ptr::null_mut();
let b = unsafe { std::mem::transmute::<*mut u64, Box<u64>>(ptr) }; but doesn't detect UB in this code: let mut ptr = std::ptr::null_mut();
let b = unsafe { std::mem::transmute::<&mut *mut u64, &mut Box<u64>>(&mut ptr) }; |
@RalfJung if you have any pointers for how to write a miri test for this PR I'm happy to have another go at it. |
This comment has been minimized.
This comment has been minimized.
@petrosagg
|
oh, amazing! I will add a testcase |
Note that that flag enables UB checks that are very strict, and goes beyond what we are sure will be UB.
|
I see. This particular test exercises very little code but I don't have a strong opinion either. If we want to avoid using this experimental feature until the validity UB rules become more established I can leave the PR as-is. Any opinions as to whether a testcase for this would be useful to have? |
Cc @the8472 who I believe authored this.
Something that exercises the UB with the current implementation would be great, even if it doesn't currently get flagged by default miri. |
These commits modify the If this was unintentional then you should revert the changes before this PR is merged. The Miri subtree was changed cc @rust-lang/miri |
@tgross35 great! Just added a miri testcase for this. I did pass the experimental flag, meaning that the test fails without the fix. |
I only did some later modifications, this was originally introduced as
The bytes are still initialized, we don't de-init the bytes when moving the value out. It happens to be the bytes of a pointer that points to a deallocated object. AIUI validity requirements are still under discussion, but leaning no in this case: rust-lang/unsafe-code-guidelines#412 That said, the slice construction is superfluous and if we can replace it with something that's easier to reason about that's fine. |
This is true. However, I would argue that the standard to which the standard library should be held to is "definitely not UB" and not "maybe UB or maybe not", especially if the fix is not particularly difficult and doesn't have a meaningful runtime impact. |
I do not think it makes sense to hold this one part of the library to a standard that the rest of it, let alone the compiler's output, does not come close to satisfying: rust-lang/unsafe-code-guidelines#412 (comment) |
…act_if` The implementation of the `Vec::extract_if` iterator violates the safety contract adverized by `slice::from_raw_parts` by always constructing a mutable slice for the entire length of the vector even though that span of memory can contain holes from items already drained. The safety contract of `slice::from_raw_parts` requires that all elements must be properly initialized. As an example we can look at the following code: ```rust let mut v = vec![Box::new(0u64), Box::new(1u64)]; for item in v.extract_if(.., |x| **x == 0) { drop(item); } ``` In the second iteration a `&mut [Box<u64>]` slice of length 2 will be constructed. The first slot of the slice contains the bitpattern of an already deallocated box, which is invalid. This fixes the issue by only creating references to valid items and using pointer manipulation for the rest. I have also taken the liberty to remove the big `unsafe` blocks in place of targetted ones with a SAFETY comment. The approach closely mirrors the implementation of `Vec::retain_mut`. Signed-off-by: Petros Angelatos <[email protected]>
Vec::extract_if
slice::from_raw_parts
safety contract in Vec::extract_if
In light of the discussion from the unsafe code guidelines repo it's clear that this PR is in a gray area as to whether it fixes UB or not. The current safety contract for I'd argue this PR is valuable on its own even if this behavior is not deemed UB in the future for two reasons:
I have removed the miri testcase that uses the experimental flag and have rephrased the commit and description of the PR to reflect this new framing. |
There is no language UB here at the moment. There is library UB but this code is inside the same library, so that is not necessarily a problem -- it boils down to a matter of preference of the libs team:
Miri does not test for library UB, so you can't add a Miri test. I don't think it's worth having a test with |
The implementation of the
Vec::extract_if
iterator violates the safety contract adverized byslice::from_raw_parts
by always constructing a mutable slice for the entire length of the vector even though that span of memory can contain holes from items already drained. The safety contract ofslice::from_raw_parts
requires that all elements must be properlyinitialized.
As an example we can look at the following code:
In the second iteration a
&mut [Box<u64>]
slice of length 2 will be constructed. The first slot of the slice contains the bitpattern of an already deallocated box, which is invalid.This fixes the issue by only creating references to valid items and using pointer manipulation for the rest. I have also taken the liberty to remove the big
unsafe
blocks in place of targetted ones with a SAFETY comment. The approach closely mirrors the implementation ofVec::retain_mut
.Note to reviewers: The diff is easier to follow with whitespace hidden.