-
-
Notifications
You must be signed in to change notification settings - Fork 12
Sticky
's non-scoped APIs (get{,_mut}
) are technically unsound
#26
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
Comments
The minimal repro is this: #[test]
fn test_bad_sticky() {
let (tx, rx) = std::sync::mpsc::channel();
std::thread::spawn(move || {
let sticky = Box::new(Sticky::new(Box::new(true)));
let static_sticky = Box::leak(sticky);
tx.send(static_sticky.get()).unwrap();
})
.join()
.unwrap();
let sticky = rx.recv().unwrap();
dbg!(sticky);
} A potential alternative solution to me appears to be to have a |
Problem is leakage of the Alas, besides the very annoying scoped API version (which is why I wish https://docs.rs/with_locals somehow got nicer language support), there is only one other API which can be fully sound: a macro one: unsafe
fn get<'must_be_local> (r: &'must_be_local Sticky<T>, _local: &'must_be_local ())
-> &'must_be_local T
…
macro_rules! safe_deref {(
$sticky:expr => let $binding:pat $(,)?
) => (
let local = ();
let $binding = match &$sticky { sticky => {
#[allow(unused_unsafe)] { unsafe {
$crate::path::to::Sticky::get(sticky, &local)
}}
}};
)} so that it can be used as: let sticky = Sticky::new(42);
…
safe_deref!(sticky => let reference);
assert_eq!(reference, &42); IdeaA slightly smoother API may be to factor this macro pattern into a helper type: pub struct LocalVariable(());
impl LocalVariable {
/// Have a macro offer this in a non-unsafe fashion:
/// `create_local!(let token);` (token would then be a `&'fn LocalVariable`)
pub unsafe fn new() -> Self {
Self(())
}
/// we can still offer a scoped API as well to remain flexible.
pub fn with_new<R>(scope: impl FnOnce(&Self) -> R) -> R {
scope(&Self(()))
}
} and then have fn get<'local>(self: &'local Sticky<T>, _proof: &'local LocalVariable) -> &'local T
/// Notice how we don't need the mut on the proof, since we just care about its non-static lifetime.
fn get_mut<'local>(self: &'local mut Sticky<T>, _proof: &'local LocalVariable) -> &'local mut T |
You are right. Since the handle can also become |
This is resolved in 2.0.0. I went with passing a proof token in which I think has a better migration experience. |
This updates fragile to 2.0.0 as 1.2.x has a soundness issue: mitsuhiko/fragile#26 Co-authored-by: Oleksandr Kylymnychenko <oleksandr@sentry.io>
Basically the following signature:
fragile/src/sticky.rs
Line 138 in 3bdcfa1
is technically unsound when the receiver is a
&'static self
(which can be achieved throughBox::leak(Box::new())
, astatic : sync::OnceCell
, etc.), since that yields a&'static T
, which is technically incorrect given thatT
is stored inthread_local!
storage (here, the registry), eventually deallocated and thus non-'static
.To illustrate, one can store the so-obtained
&'static T
in some otherthread_local!
's drop glue (let's call itFOO
), and then if the firstthread_local!
(the one backing the actual value theSticky
pointed to (the registry)) is reclaimed beforeFOO
is, the&T
in that remaining drop glue will be dangling (or at least pointing to an expired value).This is exactly why the
thread_local!
s themselves (theLocalKey
s) do not offer.get()
-like APIs, and require this scoped one instead.Sticky
(and thus,SemiSticky
) ought to offer.with{,_mut}(|it| { ... })
APIs instead 😬The text was updated successfully, but these errors were encountered: