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

(soundness regression) static mut could be avoided. #117

Closed
eddyb opened this issue Aug 23, 2018 · 9 comments
Closed

(soundness regression) static mut could be avoided. #117

eddyb opened this issue Aug 23, 2018 · 9 comments

Comments

@eddyb
Copy link
Member

eddyb commented Aug 23, 2018

At least for inline_lazy, the Option<T> can be wrapped in Cell, needing only:

unsafe impl<T: Sync> Sync for Lazy<T> {}

Then we can just have a regular static and get can take &'static self safely.

EDIT: As noted below (#117 (comment)), the use of &'static mut Lazy<T> is unsound.
Also, the impl above exists already, UB was introduced by regression at a308da1.

cc @anp @RalfJung

@RalfJung
Copy link

How is an unsafe impl Sync any better than a static mut?

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

Because it's provably correct (it only depends on Lazy::get's body) and it makes uses safe.

Right now uses of Lazy are unsafe and have to be wrapped, which moves the hazards outside of the module. E.g. today for a reentrant Lazy::get you end up with the same &'static mut Lazy<T> twice in the call stack, and miri with a proper check would scream "UB".

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

Just proves once more that static mut is an unusable mis-feature that creates UB left and right.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

Wait, it already has:

unsafe impl<T: Sync> Sync for Lazy<T> {}

Looks like it regressed in a308da1.

@eddyb eddyb changed the title static mut could be avoided. (soundness regression) static mut could be avoided. Aug 23, 2018
@RalfJung
Copy link

I do not understand the point about get. Global mutable state only works if every access cooperates, this includes readers. unsafe impl Sync can at best paper over soundness concerns.

However, I agree that this is unsound:

    pub fn get<F>(&'static mut self, f: F) -> &T
        where F: FnOnce() -> T
    {
        {
            let r = &mut self.0;
            self.1.call_once(|| {
                *r = Some(f());
            });
        }
        unsafe {
            match self.0 {
                Some(ref x) => x,
                None => std::intrinsics::unreachable(),
            }
        }

It doesn't even take reentrancy. self is alive all the time, but some other thread might be the one to win the race and initialize it.

@eddyb
Copy link
Member Author

eddyb commented Aug 23, 2018

@RalfJung Right, I ignored multi-threading, because it's easier to reason about reentrance (ironically).

That is, f is arbitrary code, which means Lazy::get can be called from it, for the same self (since Lazy::get on a static mut is what lazy_static! provides a wrapper around).

Two Lazy::get on the same callstack with same self address is UB, right?

@RalfJung
Copy link

I find it easier to reason about concurrency :P

Two Lazy::get on the same callstack with same self address is UB, right?

The question is how long the references live. But given that the outer frame has a reference that's live before and after the reentrant call -- yes, this is UB.

@KodrAus
Copy link
Contributor

KodrAus commented Aug 25, 2018

Ok, so if I'm following this we'll avoid this problem by pushing mutability inside the Lazy through a Cell and relying on Once's thwarting of attempted re-entrancy to set the value and preventing multiple live mutable references?

@eddyb
Copy link
Member Author

eddyb commented Aug 25, 2018

@KodrAus Yupp, ignoring the existence references, the current code's memory accesses are fine because of the Once, all we need to do is avoid &mut and use Cell instead.

erickt added a commit to erickt/multipart that referenced this issue Jan 19, 2019
Before this patch, multipart got into an impossible sitation with
it's dependencies. It errs with:

```
error: failed to select a version for `lazy_static`.
    ... required by package `multipart v0.15.4`
versions that meet the requirements `>= 1.0, < 1.2.0` are: 1.1.0, 1.0.2, 1.0.1, 1.0.0

all possible versions conflict with previously selected packages.

  previously selected package `lazy_static v1.2.0`
    ... which is depended on by `ring v0.13.5`
    ... which is depended on by `cookie v0.11.0`
    ... which is depended on by `rocket_http v0.4.0`
    ... which is depended on by `rocket v0.4.0`
    ... which is depended on by `multipart v0.15.4
```

This is due to ring 0.13.3 bumping lazy_static to 1.2.0 to avoid
a [soundness bug](rust-lang-nursery/lazy-static.rs#117).
This patch fixes this problem by requiring at least rust 1.24.1.

In addition, I noticed that the feature sse4 was depending on
`twoway/pcmp`, but that has been [removed](bluss/twoway#8).
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

No branches or pull requests

3 participants