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

Scoped threads violate 'dereferenceable for function call' requirement of references #101983

Closed
Tracked by #2528
RalfJung opened this issue Sep 18, 2022 · 5 comments · Fixed by #102589
Closed
Tracked by #2528
Labels
C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@RalfJung
Copy link
Member

Scopes threads as currently implemented are potentially unsound due to passing a reference to a function where the memory the reference points to is potentially deallocated while the function still runs.

Specifically, this main function here takes as implicit argument its closure environment, which contains f. That environment might just be a reference to some memory that is allocated in the caller stack frame. However the main function can keep running after their_packet got dropped (implicitly at the end of main). Then the scope might end and the memory might be deallocated all while main is still running. If the environment is just a reference, it ends up being a newtype and we will (AFAIK) add the dereferenceable attribute, meaning deallocation while the function runs is actually LLVM UB.

Miri found the error in this doc test, where the a.push(4) invalidates the &a reference that was passed to the forked-off threads. (a doesn't actually get deallocated, it just has a unique reference created to it, but deallocation is also a possibility).

To fix this properly, I think we need a language extension: we want a way to store references in a type without having alias guarantees for that reference. In terms of Stacked Borrows this means Stacked Borrows will stop recursing at that type during retags. In terms of the LLVM IR we generate it means we must not add noalias or dereferenceable attributes for references inside that type. We don't have such a type currently but I think it makes sense to make ManuallyDrop that type (or even if we add a new type for this, ManuallyDrop should use it). Also see this Zulip discussion.

@RalfJung RalfJung added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness C-bug Category: This is a bug. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Sep 18, 2022
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Sep 18, 2022
@thomcc
Copy link
Member

thomcc commented Sep 18, 2022

I believe #98517 included a fix for this.

@RalfJung
Copy link
Member Author

Doesn't look like it -- the user-provided closure seems to be handled exactly the same as currently?

@thomcc
Copy link
Member

thomcc commented Sep 18, 2022

Ah, I read too quickly and misunderstood, apologies. It solves a different potential unsoundness (one more similar to the Arc drop issue).

@RalfJung
Copy link
Member Author

Yeah, it looks like an alternative fix for the dereferenceable part of #98498.

@apiraino apiraino removed the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Sep 20, 2022
@RalfJung
Copy link
Member Author

RalfJung commented Oct 2, 2022

While the RFC for a proper fix is cooking, I am proposing a preliminary fix in #102589.

@bors bors closed this as completed in 919d6bf Oct 11, 2022
thomcc pushed a commit to tcdi/postgrestd that referenced this issue Feb 10, 2023
scoped threads: pass closure through MaybeUninit to avoid invalid dangling references

The `main` function defined here looks roughly like this, if it were written as a more explicit stand-alone function:
```rust
// Not showing all the `'lifetime` tracking, the point is that
// this closure might live shorter than `thread`.
fn thread(control: ..., closure: impl FnOnce() + 'lifetime) {
    closure();
    control.signal_done();
    // A lot of time can pass here.
}
```
Note that `thread` continues to run even after `signal_done`! Now consider what happens if the `closure` captures a reference of lifetime `'lifetime`:
- The type of `closure` is a struct (the implicit unnameable closure type) with a `&'lifetime mut T` field. References passed to a function are marked with `dereferenceable`, which is LLVM speak for *this reference will remain live for the entire duration of this function*.
- The closure runs, `signal_done` runs. Then -- potentially -- this thread gets scheduled away and the main thread runs, seeing the signal and returning to the user. Now `'lifetime` ends and the memory the reference points to might be deallocated.
- Now we have UB! The reference that as passed to `thread` with the promise of remaining live for the entire duration of the function, actually got deallocated while the function still runs. Oops.

Long-term I think we should be able to use `ManuallyDrop` to fix this without `unsafe`, or maybe a new `MaybeDangling` type. I am working on an RFC for that. But in the mean time it'd be nice to fix this so that Miri with `-Zmiri-retag-fields` (which is needed for "full enforcement" of all the LLVM flags we generate) stops erroring on scoped threads.

Fixes rust-lang/rust#101983
r? `@m-ou-se`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: This is a bug. I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-lang Relevant to the language team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants