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

Keeping references to #[thread_local] statics is allowed across yields. #49682

Open
eddyb opened this issue Apr 5, 2018 · 6 comments
Open

Keeping references to #[thread_local] statics is allowed across yields. #49682

eddyb opened this issue Apr 5, 2018 · 6 comments
Labels
A-borrow-checker Area: The borrow checker A-coroutines Area: Coroutines C-enhancement Category: An issue proposing an enhancement or a PR with one. F-coroutines `#![feature(coroutines)]` F-thread_local `#![feature(thread_local)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@eddyb
Copy link
Member

eddyb commented Apr 5, 2018

This does not affect stabilization of async fn unless #[thread_local] is also stabilized

Try on playground:

#![feature(generators, generator_trait, thread_local)]

use std::ops::Generator;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

#[thread_local]
static FOO: AtomicUsize = AtomicUsize::new(0);

fn foo() -> impl Generator<Yield = (String, usize), Return = ()> {
    static || {
        let x = &FOO;
        loop {
            let s = format!("{:p}", x);
            yield (s, x.fetch_add(1, Ordering::SeqCst));
        }
    }
}

fn main() {
    unsafe {
        let mut g = thread::spawn(|| {
            let mut g = foo();
            println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
            g
        }).join().unwrap();
        println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
        thread::spawn(move || {
            println!("&FOO = {:p}; resume() = {:?}", &FOO, g.resume());
        }).join().unwrap();
    }
}

Sample output:

&FOO = 0x7f48f9bff688; resume() = Yielded(("0x7f48f9bff688", 0))
&FOO = 0x7f48fafd37c8; resume() = Yielded(("0x7f48f9bff688", 1))
&FOO = 0x7f48f9bff688; resume() = Yielded(("0x7f48f9bff688", 0))

You can see by the pointer addresses and values inside FOO that the same location was reused for the second child thread (it's a bit harder to show a crash) - this is clearly an use-after-free.
If we had in-language async, the same problem could be demonstrated using those.

In non-generator functions, such references have function-local lifetimes and cannot escape.
With the stable thread_local! from libstd, user code gets access to the reference in a (non-generator/async) closure, which also doesn't allow escaping the reference.

cc @alexcrichton @withoutboats @Zoxc

@eddyb eddyb added I-nominated A-borrow-checker Area: The borrow checker T-compiler Relevant to the compiler 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 A-coroutines Area: Coroutines labels Apr 5, 2018
@comex
Copy link
Contributor

comex commented Apr 5, 2018

Am I right in assuming the same issue applies to implementations of thread locals in external crates, which aren't limited to unstable? e.g. https://amanieu.github.io/thread_local-rs/thread_local/index.html

edit: I'm wrong. From that page:

Per-thread objects are not destroyed when a thread exits. Instead, objects are only destroyed when the ThreadLocal containing them is destroyed.

Otherwise the API would be blatantly unsound even without generators.

edit2: ignore me; I misunderstood the scope of the problem. For the record, it doesn't apply to thread_local!, or anything else that uses a callback.

@nikomatsakis
Copy link
Contributor

triage: P-medium

Should fix, not urgent.

@nikomatsakis nikomatsakis added P-medium Medium priority and removed I-nominated labels Apr 12, 2018
@jkordish jkordish added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label May 8, 2018
@Centril Centril added F-coroutines `#![feature(coroutines)]` F-thread_local `#![feature(thread_local)]` labels Jul 28, 2019
@Centril Centril added the requires-nightly This issue requires a nightly compiler in some way. label Sep 6, 2019
@bstrie
Copy link
Contributor

bstrie commented Feb 4, 2020

If we had in-language async, the same problem could be demonstrated using those.

Now that async/await is stable, can anyone reproduce this bug using only those? I gave it a whack (playground here) and got the following error message:

error[E0712]: thread-local variable borrowed past end of function
  --> src/main.rs:10:5
   |
10 |     &FOO
   |     ^^^^ thread-local variables cannot be borrowed beyond the end of the function
11 | }
   | - end of enclosing function is here

...which suggests at least some degree of mitigation is already implemented, but it's possible that my translation (which obviously can't yield directly) did not represent the spirit of the original bug.

@Zoxc
Copy link
Contributor

Zoxc commented Feb 4, 2020

@bstrie Here's an async example which should not compile.

@Elinvynia Elinvynia added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Jun 9, 2020
@LeSeulArtichaut LeSeulArtichaut removed the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Jun 9, 2020
@ChayimFriedman2
Copy link
Contributor

Instead of disallow it, I think we should allow it but make the resulting generator !Send + !Sync.

@repnop
Copy link
Contributor

repnop commented Apr 15, 2022

I would expect this to make the resulting future !Send instead of completely disallowing its use. I have an executor which does not run off of anything but the main thread, so I'd ideally like to not need stricter synchronization than RefCell just to be able to access a static in this environment.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Apr 27, 2023
…unsound-issues, r=jackh726

Add `known-bug` tests for 4 unsound issues

This PR adds `known-bug` tests for 4 unsound issues as part of rust-lang#105107
- rust-lang#40582
- rust-lang#49682
- rust-lang#74629
- rust-lang#105782
@jackh726 jackh726 added the S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. label Apr 27, 2023
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Apr 27, 2023
…unsound-issues, r=jackh726

Add `known-bug` tests for 4 unsound issues

This PR adds `known-bug` tests for 4 unsound issues as part of rust-lang#105107
- rust-lang#40582
- rust-lang#49682
- rust-lang#74629
- rust-lang#105782
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-borrow-checker Area: The borrow checker A-coroutines Area: Coroutines C-enhancement Category: An issue proposing an enhancement or a PR with one. F-coroutines `#![feature(coroutines)]` F-thread_local `#![feature(thread_local)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness P-medium Medium priority requires-nightly This issue requires a nightly compiler in some way. S-bug-has-test Status: This bug is tracked inside the repo by a `known-bug` test. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests