Skip to content
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

Potential unsoundness in DrainProducer around drop_in_place on mutable reference #1029

Closed
Manishearth opened this issue Mar 1, 2023 · 8 comments · Fixed by #1030
Closed

Comments

@Manishearth
Copy link
Contributor

rayon/src/vec.rs

Lines 226 to 231 in 7069695

impl<'data, T: 'data + Send> Drop for DrainProducer<'data, T> {
fn drop(&mut self) {
// use `Drop for [T]`
unsafe { ptr::drop_in_place(self.slice) };
}
}

This code takes &mut Self, which itself contains an &mut [T], and runs drop glue for the elements in the slice.

While ptr::drop_in_place() does not explicitly say that the resultant value is invalid, it says it is "invalid to read". My understanding of the Rust unsafe model is that it is UB to have a live reference to a type that is invalid to read, in this case we are violating the validity constraints of &mut [T], which in turn violates validity of &mut Self.

The type does come from ManuallyDrop, but it's still UB to invalidate that whilst it has live references to it.

I think this should probably be a raw pointer + phantomdata?

@Manishearth Manishearth changed the title Potential unsoundness in DrainProducer Potential unsoundness in DrainProducer around drop_in_place on mutable reference Mar 1, 2023
@Manishearth
Copy link
Contributor Author

The usage of IterMut could have similar problems were IterMut implemented by wrapping an &mut T and an index, but it's not, so it gets away on a technicality.

@cuviper
Copy link
Member

cuviper commented Mar 2, 2023

Well this sounds a little familiar -- see #851 / #852 (and later #907), and the cited UCG#77 still isn't resolved. I think this case is actually on more solid ground, because we're not dealing with uninitialized data (dropping is different), but I'm willing to try a more conservative approach here.

The usage of IterMut could have similar problems were IterMut implemented by wrapping an &mut T and an index, but it's not, so it gets away on a technicality.

I don't see how -- the references we get from IterMut are then independent of it, as this isn't a GAT iterator. But that borrow does still tie back to wherever we got it from in the first place.

@cuviper
Copy link
Member

cuviper commented Mar 2, 2023

Do you think this suffices?

diff --git a/src/vec.rs b/src/vec.rs
index c804b0f339bb..900b3b9785bd 100644
--- a/src/vec.rs
+++ b/src/vec.rs
@@ -226,7 +226,8 @@ impl<'data, T: 'data + Send> Producer for DrainProducer<'data, T> {
 impl<'data, T: 'data + Send> Drop for DrainProducer<'data, T> {
     fn drop(&mut self) {
         // use `Drop for [T]`
-        unsafe { ptr::drop_in_place(self.slice) };
+        let slice = mem::take(&mut self.slice) as *mut [T];
+        unsafe { ptr::drop_in_place(slice) };
     }
 }
 

@Manishearth
Copy link
Contributor Author

not dealing with uninitialized data (dropping is different)

The docs on drop_in_place are kinda squirrelly about this, they say that the dropped pointer should never be read from, but it does seem like in the current plans for the unsafe model there is no difference between having a reference and reading from it.

I plan to file an issue upstream about what precisely drop_in_place does when it comes to validity.

Do you think this suffices?

No, because ultimately &mut self is still left in a potentially invalid situation: I think the field needs to be a raw pointer.

@cuviper
Copy link
Member

cuviper commented Mar 2, 2023

Why is &mut self invalid? After the mem::take, it has no connection to the pointer we're about to drop.

@Manishearth
Copy link
Contributor Author

Oh! Yes, that should be fine.

@cuviper
Copy link
Member

cuviper commented Mar 2, 2023

See #1030.

bors bot added a commit that referenced this issue Mar 2, 2023
1030: Be more cautious about drain drops r=cuviper a=cuviper

This makes a greater effort to ensure that in all cases where either
`DrainProducer` or `SliceDrain` will drop data, there are no references
left pointing to that memory. It's not actually clear that this would be
a problem anyway, as long as there's nothing accessing that zombie data,
but it's not too much trouble to avoid it.

Fixes #1029.


Co-authored-by: Josh Stone <[email protected]>
@bors bors bot closed this as completed in 98077fe Mar 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants