-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Arc::drop has a (potentially) dangling shared ref #55005
Comments
Potential fixes:
|
Turns out Clang has a similar issue, it uses |
It's a little spooky to see a situation where a region of code could do something undefined without an Anyway, in this twilight zone dangling pointers are not prohibited. Dereferencing dangling pointers is. Exposing somebody else's unsuspecting code to a dangling pointer is also very forbidden. But it's fine for dangling pointers to exist as long as they are dead. Remember: "dead" means "will not be accessed by anything below this point." Other languages don't even have a concept of reference lifetimes.
That can happen. But if it does, Thread A is guaranteed to fetch a reference count greater than 1, which then causes It would be undefined behavior for any method to be called after Honestly, I think you're a little too attached to the "Stacked Borrows" model - this is a situation where it doesn't work and rather than trying to understand why the model should be changed, you're arguing that the source code of stdlib should be declared unsound. If Rust continues in the tradition of LLVM, the uniqueness of |
The immediate problem is not about Rust, safe or unsafe, or any proposed formal semantics for it. rustc emits IR that has technically undefined behavior, because there is a pointer that is claimed to be Aside from that, there is the task of justifying the LLVM IR that rustc emits with Rust-level rules -- at minimum, in every case where emitted LLVM IR has UB, the input Rust code also needs to have UB to be able to claim rustc is correct. This issue points out a case where previous attempts at justifying the |
Option one:
for resolving this bug was tried in here: #58611 |
I'm nominating this issue -- and tagging it for @rust-lang/lang -- in response to some comments by @jamesmunns in today's Project Leadership Sync meeting. In particular, @jamesmunns pointed out that this issue around the deferenceable attribute has potentially large repercussions in the embedded space, and cited @japaric's RFC as well as a number of issues around the embedded space (see the minutes). I've got to dig a bit deeper here but it seems like it'd be good to check-in on the current thinking here. I will try to catch up on the RFC and some of the proposed changes and see if I can post any notes. Without having done so, I would say that, as far as I know, we still want to keep the dereferenceable attribute, so I expect some form of these changes will be required. (I suppose omething like the [dereferencable on entry proposal(https://lists.llvm.org/pipermail/llvm-dev/2018-July/124555.html) might also be good, does anyone know if that's made progress? I'm going to assume not.) |
@nikomatsakis I have done some work around this. But actually that extended visible API surface by one method. It resolves the problem at a cost of that. |
I could be wrong, but it seems like LLVM now supports |
to
has not yet happened. |
Ah so the tentative plan in LLVM now is to weaken That seems bad though. Quoting:
So, e.g., a function that takes a reference and also drops an unrelated Maybe some from the Rust side should chime in an raise this as a concern? |
I forgot about this before but there's a (not yet committed) patch introducing a new attribute for "dereferencable forever" at https://reviews.llvm.org/D61652 |
See #65796 for another instance: there's a call to |
Unfortunately I have no idea what you mean by this rather vague proposal. Please describe your changes via concrete code / a diff that applies to the current code. |
Again, we don't know if it's possible to do it that way. The basic idea is we'd allocate a struct on the stack, and place a pointer to it in the ArcInner. we'd then check the ArcInner to see if it was modified and attempt to clear it. if it was modified, we wait for a signal and... ah, we guess this would only shift the problem to AtomicPtr... Hmm... ... yeah nvm, we guess the only real solution is to tell the abstract machine to just accept it as-is somehow. |
#98498 is another instance of this -- or rather, after doing the easiest fix, it becomes an interesting instance of this -- except that this time, not the entire memory |
#98017 seems like a potentially large change to fix a relatively smaller issue. Would the following change not be sufficient?: - if self.inner().strong.fetch_sub(1, Release) != 1 {
- return;
- }
+ let strong_ptr = self.inner().strong.as_mut_ptr();
+ unsafe {
+ if core::intrinsics::atomic_xsub_rel(strong_ptr, 1) != 1 {
+ return;
+ }
+ }; In this case, the current thread would have no references to I know I have commented this before and I don't mean to distract from the conversation, just a bit confused why a change to rustc itself would be required here. I worry that limiting the use of |
Given that approximately the same issue has come up in If we went that route of not allowing references in this pattern, I think we should consider expanding the API of atomics. Creating functions explicitly designed to be safe for this "may signal that self can be deallocated" pattern would greatly help guide people in the right direction. |
I do not think that reasoning is very good. Numerous crates including We should not attempt to codify and bless existing practice, because that is largely based on whatever appears to work with the current implementation of the language, and may not lend itself to a coherent set of rules. It seems clear at this point that we cannot have a simple set of rules which permit both the applications and optimizations we would like Rust to have and also accept all existing code, or even all code written by "experts". There will be give on both the language and the use of the language. I don't have a strong opinion on the right way to resolve this situation, but I will point out that losing |
My personal preference to resolve the situation is to also say that when a shared ref points to a |
855: Miri: enable preemption again r=taiki-e a=RalfJung Miri has a [work-around](rust-lang/miri#2248) for rust-lang/rust#55005 now. Also, "check-number-validity" is on by default these days. And I am not sure why "compare-exchange-weak-failure-rate=0.0" was set so let's see what happens when we remove it. :) Co-authored-by: Ralf Jung <[email protected]>
855: Miri: enable preemption again r=taiki-e a=RalfJung Miri has a [work-around](rust-lang/miri#2248) for rust-lang/rust#55005 now. Also, "check-number-validity" is on by default these days. And I am not sure why "compare-exchange-weak-failure-rate=0.0" was set so let's see what happens when we remove it. :) 858: Ignore clippy::let_unit_value lint r=taiki-e a=taiki-e Co-authored-by: Ralf Jung <[email protected]> Co-authored-by: Taiki Endo <[email protected]>
855: Miri: enable preemption again r=taiki-e a=RalfJung Miri has a [work-around](rust-lang/miri#2248) for rust-lang/rust#55005 now. Also, "check-number-validity" is on by default these days. And I am not sure why "compare-exchange-weak-failure-rate=0.0" was set so let's see what happens when we remove it. :) 858: Ignore clippy::let_unit_value lint r=taiki-e a=taiki-e Co-authored-by: Ralf Jung <[email protected]> Co-authored-by: Taiki Endo <[email protected]>
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
do not mark interior mutable shared refs as dereferenceable My proposed solution to rust-lang#55005.
@RalfJung Can we solve this by changing Drop trait API? Something like this: trait Drop{
fn drop(&mut self);
fn drop_ptr(mut self: NonNull<Self>){
unsafe{
// Only case when direct call to `Drop::drop` is allowed.
Drop::drop(self.as_mut());
}
}
} All existing code would just work because it delegates to old methods that accept the reference. impl Drop for MyTypeWithPtr{
fn drop(&mut self){ unreachable!("We use only pointer based deallocation") }
fn drop_ptr(mut self: NonNull<Self>){
// Since we created references inside function, they are not dereferencable.
// And we can check all our conditions using pointer based API
// to avoid having living references to data freed in separate thread.
}
} This way we would solve issues caused by dropping code like this without giving up |
It's not the reference in pub fn fetch_sub(&self, val: $int_type, order: Ordering) -> $int_type {
unsafe { atomic_sub(self.v.get(), val, order) }
// HERE
} Anyway, with #98017 having landed, I think we can finally close this issue. :) We barely managed to do it in less than 4 years. :D |
`Latch::set` can invalidate its own `&self`, because it releases the owning thread to continue execution, which may then invalidate the latch by deallocation, reuse, etc. We've known about this problem when it comes to accessing latch fields too late, but the possibly dangling reference was still a problem, like rust-lang/rust#55005. The result of that was rust-lang/rust#98017, omitting the LLVM attribute `dereferenceable` on references to `!Freeze` types -- those containing `UnsafeCell`. However, miri's Stacked Borrows implementation is finer- grained than that, only relaxing for the cell itself in the `!Freeze` type. For rayon, that solves the dangling reference in atomic calls, but remains a problem for other fields of a `Latch`. This easiest fix for rayon is to use a raw pointer instead of `&self`. We still end up with some temporary references for stuff like atomics, but those should be fine with the rules above.
`Latch::set` can invalidate its own `&self`, because it releases the owning thread to continue execution, which may then invalidate the latch by deallocation, reuse, etc. We've known about this problem when it comes to accessing latch fields too late, but the possibly dangling reference was still a problem, like rust-lang/rust#55005. The result of that was rust-lang/rust#98017, omitting the LLVM attribute `dereferenceable` on references to `!Freeze` types -- those containing `UnsafeCell`. However, miri's Stacked Borrows implementation is finer- grained than that, only relaxing for the cell itself in the `!Freeze` type. For rayon, that solves the dangling reference in atomic calls, but remains a problem for other fields of a `Latch`. This easiest fix for rayon is to use a raw pointer instead of `&self`. We still end up with some temporary references for stuff like atomics, but those should be fine with the rules above.
1011: Use pointers instead of `&self` in `Latch::set` r=cuviper a=cuviper `Latch::set` can invalidate its own `&self`, because it releases the owning thread to continue execution, which may then invalidate the latch by deallocation, reuse, etc. We've known about this problem when it comes to accessing latch fields too late, but the possibly dangling reference was still a problem, like rust-lang/rust#55005. The result of that was rust-lang/rust#98017, omitting the LLVM attribute `dereferenceable` on references to `!Freeze` types -- those containing `UnsafeCell`. However, miri's Stacked Borrows implementation is finer- grained than that, only relaxing for the cell itself in the `!Freeze` type. For rayon, that solves the dangling reference in atomic calls, but remains a problem for other fields of a `Latch`. This easiest fix for rayon is to use a raw pointer instead of `&self`. We still end up with some temporary references for stuff like atomics, but those should be fine with the rules above. Co-authored-by: Josh Stone <[email protected]>
Discovered by @Amanieu on IRLO. Quoting their report:
Arc::drop
contains this code:Once the current thread (Thread A) has decremented the reference count, Thread B could come in and free the
ArcInner
.The problem becomes apparent when you look at the implementation of
fetch_sub
:Note the point marked HERE: at this point we have released our claim to the
Arc
(as in, decremented the count), which means that Thread B might have freed theArcInner
. However the&self
still points to the strong reference count in theArcInner
-- so&self
dangles.Other instances of this:
The text was updated successfully, but these errors were encountered: