-
Notifications
You must be signed in to change notification settings - Fork 179
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
Yoke vs noalias #2095
Comments
Hmm, so Yokeable enforces covariance, which ought to protect us from issues like this involving interior mutability? That said, we have no such constraints on Carts. In general Yoke should probably just disallow carts with interior mutability, we can perhaps do that by enforcing that the cart does not deref to a type containing interior mutability? We might need our own trait for that, That does bring up the question: does this UB still exist with In our case most carts will be some pointer type wrapping some kind of simple data buffer ( Do you think that would be sufficient to protect us from this bug? |
Apologies for the below footnote essay, this is a complicated topic involving open questions about how to write sound unsafe Rust. The footnotes should just be optional context. The 100% surefire way is to store an I've been meaning to write a crate to do this to suggest people replace While that approach is 100% bulletproof w.r.t. aliasing5, it's not necessarily necessary. Instead, yoke could use a refined version of
it's just that none of the std types ever actually guaranteed6 this. However, if you're willing to assume a reasonable quality of implementation from your standard library, we could define our own A third status quo option is to bet on yoke's usage just never breaking. There is a lot more code out there doing yoke-like things (c.f. owning_ref, rental) and in a lot more questionably sound ways. Make a Miri job so that we'll know if/when it breaks; it's imho very unlikely that the situation will be made worse without a way to get the behavior we want from uniquely owning std pointers8. Note that the problem is not shared mutability in the pointee (that's absolutely fine) and not even shared mutability before indirection (that's disallowed by TL;DR (I don't blame you): Three main options:
Any change for soundness I think also needs to replace access to Footnotes
|
Thanks for this in-depth analysis! I read it a while ago but didn't have an opportunity to reply, sorry.
Note: This API exists so that it's possible to e.g. clone the underlying Rc. In general I would like to be able to use
So I think here's where I feel like it's upon the UCG to come up with ways to disable this. Namely, having some kind of In general my attitude towards this is:
It feels like we're strongly in the second case here: All the "fixes" for this reduce the flexibility of Yoke, and it's not at all clear to me that the UCG's model so far really has to mandate that: they just haven't figured out escape hatches. Thoughts? |
@Manishearth is "Utilities 1.0" the correct milestone for this issue? |
Yes, though to some extent whether or not this is fixable by then depends on upstream |
I agree inasmuch as I also think Even in a future where If you want to test the strict version, we now have Nobody wants to break the ability to do I would consider a miri CI job for yoke sufficient to mark the issue as closed, unless you want to leave it open for further tracking. (Probably also with a |
I'm happy to leave this open for tracking but a CI job is probably a good idea anyway |
This comment was marked as off-topic.
This comment was marked as off-topic.
@mvtec-bergdolll The issue tracker is not the relevant place to ask questions about why a crate exists (ask on the currently active reddit thread instead?), but the answer basically is: those crates are internal to a type and do not support the use cases here. The miri issues are largely due to an area of UB that is still being pinned down so I'm generally happy with waiting a bit until we have more concrete rules and writing mitigations then. I may introduce a stopgap trait till then |
This comment was marked as off-topic.
This comment was marked as off-topic.
Reply to minimized@mvtec-bergdolll Any crate which does what zero-copy (actually: zero-alloc) yoking requires boils down to ....but, I found a potential implementation change that shouldn't require changing the API1 any. It's not pretty, but... if we wrap the cart in (I tried By my thermometer, it looks like we're currently very slightly biased towards There remains the case of Footnotes
|
@mvtec-bergdolll Firstly, that's a false equivalence; there is no "std" self-referential thing, Before designing this I did a survey of the existing self-referential stuff and found it lacking for our use cases. I do not feel particularly obliged to spend effort explaining the details in response to someone who has at this point violated an explicit boundary set about where such questions should go (though I appreciate @CAD97 trying!). This issue is likely going to be a complicated one, I do not wish for it to get polluted with irrelevant chatter. |
@Manishearth sorry for the perceived derailing of the discussion. I've personally seen too many projects and blog posts talk about their own version of self-referentiell structs in Rust, and more often than not their reason for going with something custom did not hold up to closer scrutiny, nor was their code sound. Maybe it's my reading comprehension and I missed some obvious part, I didn't understand the difference to other heap allocated self-referential structs from the Documentation. A section about that might help avoid future confusion. |
Tracking context: there's a new RFC that proposes a proper way out: If this RFC is accepted as currently written, it will be sufficient to wrap the cart in I believe it very likely that at a minimum If we would like to preemptively adopt the likely solution, we can wrap the cart in Note that this isn't an exclusively theoretical concern; (At this point I'm not 100% certain how allowing access to Footnotes
|
Small update: Miri's default is currently to do field retagging for "small" structs only; the ones where rustc currently emits This means consumers will be flagged for miri UB if 1) they use the default However, I made an interesting observation today when discussing this unsoundness elsewhere: I believe that due to the fact This doesn't change the fact that Rust-level UB still exists under the Stacked Borrows model as implemented by Miri, but it does provide a buffer against this turning into an actual potential miscompilation. |
Finding this late, but if the trait CloneFromRaw: Deref {
fn clone_from_raw(ptr: *const Self::Target) -> Self;
}
impl<T: ?Sized> CloneFromRaw for Rc<T> {
fn clone_from_raw(ptr: *const T) -> Self {
unsafe {
Rc::increment_strong_count(ptr);
Rc::from_raw(ptr)
}
}
} and even for Box like: impl<T> CloneFromRaw for Box<T> {
fn clone_from_raw(ptr: *const T) -> Self {
unsafe { Box::new((&*ptr).clone()) }
}
} It seems like this is probably gonna go in the |
Since I haven't seen it above, here's an example triggering a Miri error: #[test]
fn box_noalias() {
let x: Yoke<&'static u8, Box<u8>> = Yoke::attach_to_cart(Box::new(0), |data| data);
// The move into `x` reborrows the Box (cart), thus invalidating the yoke.
assert_eq!(**x.get(), 0);
} Interestingly this works fine with Tree Borrows, because nothing here is getting mutated. I am not sure if it is possible to mutate the yoke while the |
It's possible to mutate the (continued from #3735 (comment)) (context: is it sufficinet to wrap the cart in We could make The primary purpose of that method is doing things like cloning I've been holding off on resolving that until other things are settled (it's a bit hard to write safety docs for an external unsafe API when the unsafety it exhibits is not completely well documented). However if you think there's a clear path forward there, I can try and resolve it. Given that |
(Since I wrote the last comment @RalfJung already responded on the other thread and said)
@RalfJung note that (non-StableDeref carts are not always safe to construct; but they are usable; the primary use case is stuff like |
IIUC, this issue is about the cart having noalias requirements which are violated since the yoke does alias. Those requirements only come up when the cart shows up by-value (unless we decide to apply noalias recursively, but I doubt we will do that). So I don't see how Mutation could be an issue but if it's never allowed to mutate the cart or the "part of the yoke that borrows from the cart" then that circumvents those problems.
Why would that be a useful restriction? I can obviously create aliasing |
Aha, I see. If by-value is the problem then we should be fine! Great, I've been hopign to resolve this issue without impacting functionality for a while!
Ah, I misunderstood your comment. |
Ah, annoying; we do want the |
Yeah I don't think I see the necessity for that bound. |
Mostly a note for myself: while the cart is the bigger retag hazard, the yoke is also necessarily (The If/when you wrap the cart as well, that will indeed resolve this issue. The one caveat will be around |
That's #3696. |
Even with
C: StableDeref
, moving the cart can potentially invalidate the yoke.However, miri does not by default report any errors when using yoke. This is due to the fact that miri's aliasing analysis/retagging does not by-default recurse into private fields, so the box's uniqueness is never asserted, along with never giving mutable access to the carted data meaning uniqueness never needs to be asserted.
-Zmiri-retag-fields
exists to opt into the retagging of fields, which will surface this potential UB.This is mostly to note that this potential issue is known; feel free to close if this is considered a non-issue.
Related:
noalias
Kimundi/owning-ref-rs#49The text was updated successfully, but these errors were encountered: