-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Timely mem::drop execution? #1850
Comments
I'm not sure "time" has any say in this. Either you found a bug (in which case, we should investigate it as such) or your testing doesn't reveal whether the destructor was called but some other related property. |
This isn't exactly an issue with drops being delayed:
I would hazard a guess that this does not affect mutexes at all. Rather, it's something wrong with the write in particular. IIRC it's undefined behavior to rely on the stack (inline) contents of a value after it is dropped, which may be optimizing the For the context of https://github.com/isislovecruft/curve25519-dalek/issues/11, as long as the value is boxed you should be okay. It's not UB to rely on modifications to random non-inline (heap or stack reference) values that may be pointed to by the thing you're dropping. That's how Rc/Arc/Mutex work. |
It works with a boxed value:
(you can remove the prints and it will still work) Basically, don't expect writes to the inline stack part of a value during a destructor to stick around afterwards. |
The volatile thing is definitely weird - but there's of course a much much better to box secrets. |
I was wrong, there's no value sensitivity wrt zero and nonzero. I forgot to remove the assert in the destructor 😄
|
Looking at the IR for debug mode https://is.gd/NsBeic (Replaced the assert with a new function to remove noise from the code, ): https://gist.github.com/Manishearth/f911b210a93c4031b927e3c0485f8174
That code is the code called immediately after the array is initialized (the whole slice-loop stuff). You'll notice that a pair each of I initially assumed that the read in the assert was being hoisted, because I thought that You can see that happening here, the IR creates a copy before passing it to In conclusion, I don't see a Rust bug here; it's operating on the guarantees it assumes in unsafe code, on code that exhibits undefined behavior. Nor do I see any way to improve on this; the way moves work in Rust is pretty fundamental; stuff on the stack will be copied around even if you don't ask it to. The correct solution for secret-wiping for crypto is to box it. |
Interesting. If one wants this on the stack, then one wants a method to limit copies that goes beyond anything that rust currently provides. Thanks! There are obviously situations where one needs cryptography but dynamic allocation does not yet exist, like say HSMs. All that sounds like that's an issue for another day though. Worst case, you could spin up an temporary allocator or something. Did you use
|
A reference would work too. But then it's harder to ensure that the original array you borrow from is cleared. But possible. You basically need to ensure that such an array is only used to provide clearing slices. You can write a macro that stack allocates a zeroed array, and provides you with a wrapped mutable slice to it. The wrapper has a clearing dtor. Done. For temporary allocators it would use a reference, so that works too. Ideally you'd use an arena-like model where the arena itself is zero initialized, and you can borrow slices from it via a method, but this method returns wrapped slices that have a clearing dtor. |
Yeah, I edited my original comment because I realized the issues with references. Ideally, one allocates cryptographic material in a heap protected by Afaik, there is nothing besides
I suppose this inner |
This is because The reason box is special is mostly a holdover from the days when Box was a builtin type There's no fancy type conversion involved, it's just funky internals. |
Ok thank you! I also worried that Ideally, one should probably update tars to the new allocator traits or something. I've post a quick and dirty crate to do this the cheap way discussed here however : https://github.com/burdges/zerodrop-rs |
You can just use Box here. The allocators stuff is still unstable, you probably don't want to rely on it. Btw, for zerodrop-rs, just use |
Also, please don't call /// Zero a `ZeroDrop<T>` when dropped.
impl<T> Drop for ZeroDropDrop<T> where T: Drop+Default {
#[inline(never)]
fn drop(&mut self) {
drop(mem::replace(&mut *self.0, Default::default()));
}
} |
Yes, I've too much to do to worry about allocators anytime soon. ;) I used In fact, I'll need to replace I'll look into |
You can do
|
Appears
so there is still a second inner drop, but only dropping the value returned by |
Yeah, drop_in_place used like that is fine. |
I've found that
mem::drop
does not necessary run anyplace near where it gets called, which likely results inMutex
orRwLock
guards being held during expensive computations.As a simple example, I've made the following test for a zeroing drop of cryptographic material work by using
unsafe { ::std::intrinsics::drop_in_place(&mut s); }
instead of::std::mem::drop(s)
.This test fails if I use
::std::mem::drop(s)
or evenAt first, I imagined this was due to CPU pipelining, except this test still fails if I uncomment the sleep line or the 10k invocations of SHA3, or use an
atomic_fense
, so the compiler looks like the guilty party.There are two obvious scenarios where this seems problematic:
First, these zeroing drop calls should run eventually, but the longer the secret remains on the stack the greater the risk it gets compromised via side channel attacks, like being swapped to disk.
Second, we might run an expensive computation while holding a
Mutex
orRwLock
guard that unlocks when dropped. We might even run code needing additional locks, thereby risking deadlocks.In both case, one could fix the problem by calling
drop_in_place
, but I'd think that creates use after free risks, although maybe it works if we do something fancier likeIs there any safe way to ensure a drop happens before some expensive computation, system call, etc.? I suppose returning from
fn
usually does so, but mydrop_now
proves this sometimes fails.The text was updated successfully, but these errors were encountered: