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

Fix soundness issue in Yoke::attach_to_cart() around implied bounds #2949

Merged
merged 11 commits into from
Jan 23, 2023

Conversation

Manishearth
Copy link
Member

@Manishearth Manishearth commented Jan 4, 2023

Fixes #2926

The reasoning can be found in the safety docs added.

There are two potential fixes here. One is to force Yoke to always be invariant over any lifetimes found in the cart. This is unfortunate, since Yoke<_, &[u8]> is a useful cart and there's no actual safety reason for that to need to be invariant. Invariant lifetimes are annoying to work with. However, the safe function attach_to_cart() will support all carts, including ones with wacky borrowing.

The alternative is to enforce C::Target: 'static in attach_to_cart(). This will still allow Yoke<_, &[u8]> to be constructed with the method, but if there are borrows behind the deref, they will have to be constructed unsafely via replace_cart(). That's unfortunate but retains maximum flexibility for the user. I prefer this.
The latter is what is implemented.

@jira-pull-request-webhook

This comment was marked as outdated.

utils/yoke/src/yoke.rs Outdated Show resolved Hide resolved
@steffahn
Copy link
Contributor

steffahn commented Jan 4, 2023

You can define an invariant marker using PhantomData<fn(T) -> T> or PhantomData<fn() -> Cell<T>> which avoids the need for an unsafe implementation of Send or Sync

@Manishearth
Copy link
Member Author

oh beautiful, thanks. Forgot you could do that.

@sffc
Copy link
Member

sffc commented Jan 6, 2023

What are example call sites that work in option 1 but not option 2 and vice versa?

@Manishearth
Copy link
Member Author

What are example call sites that work in option 1 but not option 2 and vice versa?

Option 1 (force invariance):

  • You can have a Cart of type Box<&[..]> (Yoke<Y, Box<&[u8]>>)
  • You cannot treat carts with borrows, _including the cart type &[u8] as covariant: so Yoke<Y, &[u8]> will have a pinned lifetime when it doesn't need to

Option 2:

  • You can have a Cart of type Box<&[..]> (Yoke<Y, Box<&[u8]>>), BUT you cannot create it with attach_to_cart(); so you cannot create it safely.
  • Yoke<Y, &[u8]> will be quite flexible and you can pass it around easily

Copy link
Member

@sffc sffc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see; I think we definitely want Yoke<T, &[u8]> to have a covariant lifetime.

@sffc
Copy link
Member

sffc commented Jan 10, 2023

Is there a difference between Option 1 and Option 2 regarding the ability to borrow local variables, like you can apparently do now?

@Manishearth
Copy link
Member Author

Is there a difference between Option 1 and Option 2 regarding the ability to borrow local variables, like you can apparently do now?

In the attach_to_cart closure? No. As the cart itself? Both allow it, Option 1 forces the Yoke of such carts to be invariant, making it a bit more awkward to use.

At this point I'm heavily leaning towards Option 2 and I think we should just do that, I just need to update this PR.

@Manishearth Manishearth requested a review from sffc January 10, 2023 23:26
utils/yoke/src/zero_from.rs Outdated Show resolved Hide resolved
utils/yoke/src/yoke.rs Show resolved Hide resolved
@CAD97
Copy link
Contributor

CAD97 commented Jan 13, 2023

(PR description should be updated to state that option 2 is taken by the PR instead of option 1)

sffc
sffc previously approved these changes Jan 13, 2023
@Manishearth
Copy link
Member Author

Hmm, is the IsCovariant stuff even meaningful now? I don't think it enables anything

@Manishearth
Copy link
Member Author

@sffc are you fine with me removing IsCovariant? I can't remember why we added it, I think we wanted it for trait objects but we don't need it anymore

@Manishearth
Copy link
Member Author

Manishearth commented Jan 15, 2023

This change doesn't break it but it breaks any meaningful tests of IsCovariant since it mostly useful for trait objects with nested borrows

@sffc
Copy link
Member

sffc commented Jan 16, 2023

IsCovariant originated in #1041 to fix #760. The line of code using IsCovariant in a bound no longer exists, so it seems harmless for ICU4X to remove the trait, but we should consider what alternatives people with a problem similar to #760 should use now.

Alternatively, should we use C: IsCovariant as a bound instead of C::Target: 'static? I think that was floated as a third option but it was not pursued?

@Manishearth
Copy link
Member Author

Manishearth commented Jan 17, 2023

IsCovariant originated in #1041 to fix #760. The line of code using IsCovariant in a bound no longer exists, so it seems harmless for ICU4X to remove the trait, but we should consider what alternatives people with a problem similar to #760 should use now.

I still think that carts with complex borrowing are probably a sign of bad design (especially nested yoking like we were doing there); and we should not aim to support them in our main attach_to_cart() function. For these use cases I'd rather expose more unsafe APIs so that people can opt in to this stuff, because trait objects and variance are a particularly tricky zone to reason about even if we do use traits like IsCovariant (I'm very concerned about some niche soundness issues there). Note that IsCovariant also required unsafe code to be useful, and Yoke didn't really know about it.

We still support trait objects as carts, just not trait object carts where the borrowing is hidden behind the trait object, constructed via attach_to_cart().

We may even be able to expose safe APIs for some of the use cases here, but I'd want to separate it from the primary attach_to_cart API either way.

Alternatively, should we use C: IsCovariant as a bound instead of C::Target: 'static? I think that was floated as a third option but it was not pursued?

That requires far more IsCovariant implementations and it means you need to implement that unsafe trait for every potential cart you ever need. We currently do not require custom derives on carts.

@Manishearth
Copy link
Member Author

I removed IsCovariant. If the use case crops up again we can try to design for it more clearly, or just bring IsCovariant back (the rest of this PR does not break IsCovariant, it just makes it harder to write a useful example, and I'm a bit uncomfortable writing one without thinking a lot about soundness)

@Manishearth Manishearth mentioned this pull request Jan 23, 2023
@Manishearth Manishearth requested a review from sffc January 23, 2023 17:55
@Manishearth Manishearth merged commit 78e6a90 into unicode-org:main Jan 23, 2023
@Manishearth Manishearth deleted the yoke-soundness branch January 23, 2023 20:07
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

Successfully merging this pull request may close these issues.

yoke soundness issue with non-'static and contravariant cart, using Yoke::attach_to_cart + Yoke::get.
4 participants