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

std::thread::local internals allow race conditions in safe but unstable code. #43733

Closed
eddyb opened this issue Aug 8, 2017 · 10 comments
Closed
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-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Comments

@eddyb
Copy link
Member

eddyb commented Aug 8, 2017

(try on playpen)

#![feature(const_fn, drop_types_in_const, thread_local_internals)]

type Foo = std::cell::RefCell<String>;

static __KEY: std::thread::__FastLocalKeyInner<Foo> =
    std::thread::__FastLocalKeyInner::new();
        
fn __getit() -> std::option::Option<
    &'static std::cell::UnsafeCell<
        std::option::Option<Foo>>>
{
    __KEY.get()
}

static FOO: std::thread::LocalKey<Foo> =
    std::thread::LocalKey::new(__getit, Default::default);

fn main() {
    FOO.with(|foo| println!("{}", foo.borrow()));
    std::thread::spawn(|| {
        FOO.with(|foo| *foo.borrow_mut() += "foo");
    }).join().unwrap();
    FOO.with(|foo| println!("{}", foo.borrow()));
}

Discovered while working on a sound fix for #17954. The problem is this decision which prevents the thread_local! macro from using unsafe to state that it actually has a #[thread_local] static to use in LocalKey. Without that, anything of 'static lifetime (which is btw quite wrong for #[thread_local]) and the right type can be used, from safe code.

While we can change the APIs all we want, going back to the correct solution of #[allow(unsafe_code)] could be a breaking change, without unsafety hygiene.

cc @rust-lang/libs

@eddyb eddyb added I-nominated I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. labels Aug 8, 2017
@eddyb
Copy link
Member Author

eddyb commented Aug 8, 2017

@Manishearth
Copy link
Member

We can probably introduce a way for #[allow] to punch through forbid the same way allow_internal_unstable does. But it might be tricky.

@eddyb
Copy link
Member Author

eddyb commented Aug 8, 2017

@Manishearth Exactly my thoughts but it'd be nice if we had unsafe hygiene for this.
However, if #[allow_internal_unsafe] is fine for this, it would not be hard to implement.

@Manishearth
Copy link
Member

A slight hole in that would be that now user-written unsafe code in thread_local!() won't be caught by the lint.

....... I'm not sure what to do about that. deny(unsafe)/forbid(unsafe) is something I'd rather keep as sound as possible (yes, there are ways to circumvent it, but assuming a "non-malicious but non-smart" actor -- i.e. all of us programmers writing terrible code -- it's nice when these things are perfect)

@eddyb
Copy link
Member Author

eddyb commented Aug 8, 2017

@Manishearth #[allow_internal_unstable] already doesn't have that issue, it's hygienic.

@Manishearth
Copy link
Member

Oh, right!

@eddyb
Copy link
Member Author

eddyb commented Aug 8, 2017

@arielb1 suggested using item hygiene from the new macros. So I added #[feature(macro_decl)] to libstd, changed __thread_local_inner to be a pub macro item and I got 275 missing stability, documentation, etc. errors, because private items now "appear to be" macro-accessible.
It's reproducible with just pub macro foo() {} in src/libstd/lib.rs.

So #[allow_internal_unstable] remains the only way forward for now.

@est31
Copy link
Member

est31 commented Aug 8, 2017

Without that, anything of 'static lifetime (which is btw quite wrong for #[thread_local])

rust-lang/rfcs#1705

@eddyb
Copy link
Member Author

eddyb commented Aug 8, 2017

@est31 'thread is unsound/uncheckable. See sound fix at #43746.

@Mark-Simulacrum Mark-Simulacrum added the C-bug Category: This is a bug. label Aug 10, 2017
bors added a commit that referenced this issue Aug 11, 2017
Check #[thread_local] statics correctly in the compiler.

Fixes #43733 by introducing `#[allow_internal_unsafe]` analogous to `#[allow_internal_unstable]`, for letting a macro expand to `unsafe` blocks and functions even in `#![forbid(unsafe_code)]` crates.

Fixes #17954 by not letting references to `#[thread_local]` statics escape the function they're taken in - we can't just use a magical lifetime because Rust has *lifetime parametrism*, so if we added the often-proposed `'thread` lifetime, we'd have no way to check it in generic code.
To avoid potential edge cases in the compiler, the lifetime is actually that of a temporary at the same position, i.e. `&TLS_STATIC` has the same lifetime `&non_const_fn()` would.

Referring to `#[thread_local]` `static`s at compile-time is banned now (as per PR discussion).

Additionally, to remove `unsafe impl Sync` from `std::thread::local::fast::Key`, `#[thread_local]` statics are now not required to implement `Sync`, as they are not shared between threads.
bors added a commit that referenced this issue Aug 12, 2017
Check #[thread_local] statics correctly in the compiler.

Fixes #43733 by introducing `#[allow_internal_unsafe]` analogous to `#[allow_internal_unstable]`, for letting a macro expand to `unsafe` blocks and functions even in `#![forbid(unsafe_code)]` crates.

Fixes #17954 by not letting references to `#[thread_local]` statics escape the function they're taken in - we can't just use a magical lifetime because Rust has *lifetime parametrism*, so if we added the often-proposed `'thread` lifetime, we'd have no way to check it in generic code.
To avoid potential edge cases in the compiler, the lifetime is actually that of a temporary at the same position, i.e. `&TLS_STATIC` has the same lifetime `&non_const_fn()` would.

Referring to `#[thread_local]` `static`s at compile-time is banned now (as per PR discussion).

Additionally, to remove `unsafe impl Sync` from `std::thread::local::fast::Key`, `#[thread_local]` statics are now not required to implement `Sync`, as they are not shared between threads.
@joboet
Copy link
Member

joboet commented Sep 25, 2023

The regression test for this issue caused build failures for me in #116123. Instead of trying to rework it to fit the new API, is everyone involved here fine with me removing it? IIUC, it only tests unstable, internal API of std, which is being reworked as part of #110897 anyway.

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-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

5 participants