Is there any safe way to ensure that an arbitrary drop occurs before costly computing?

I found that mem::drop does not need to be run next to where it is called, which probably leads to the Mutex or RwLock being held during expensive computations. How can I control when calling drop ?

As a simple example, I performed the following test to drop cryptographic material trimming using unsafe { ::std::intrinsics::drop_in_place(&mut s); } unsafe { ::std::intrinsics::drop_in_place(&mut s); } instead of the simple ::std::mem::drop(s) .

 #[derive(Debug, Default)] pub struct Secret<T>(pub T); impl<T> Drop for Secret<T> { fn drop(&mut self) { unsafe { ::std::intrinsics::volatile_set_memory::<Secret<T>>(self, 0, 1); } } } #[derive(Debug, Default)] pub struct AnotherSecret(pub [u8; 32]); impl Drop for AnotherSecret { fn drop(&mut self) { unsafe { ::std::ptr::write_volatile::<$t>(self, AnotherSecret([0u8; 32])); } assert_eq!(self.0,[0u8; 32]); } } #[cfg(test)] mod tests { macro_rules! zeroing_drop_test { ($n:path) => { let p : *const $n; { let mut s = $n([3u8; 32]); p = &s; unsafe { ::std::intrinsics::drop_in_place(&mut s); } } unsafe { assert_eq!((*p).0,[0u8; 32]); } } } #[test] fn zeroing_drops() { zeroing_drop_test!(super::Secret<[u8; 32]>); zeroing_drop_test!(super::AnotherSecret); } } 

This test fails if I use ::std::mem::drop(s) or even

 #[inline(never)] pub fn drop_now<T>(_x: T) { } 

Obviously, use drop_in_place for the test so that the buffer is nullified, but I fear that calling drop_in_place in Mutex or RwLock may lead to use after free.

These two guards can be handled using this approach:

 #[inline(never)] pub fn drop_now<T>(t: mut T) { unsafe { ::std::intrinsics::drop_in_place(&mut t); } unsafe { ::std::intrinsics::volatile_set_memory::<Secret<T>>(&t, 0, 1); } } 
+5
source share
2 answers

The answer to this question is https://github.com/rust-lang/rfcs/issues/1850 :

In debug mode, any call to ::std::mem::drop(s) physically moves s to the stack, so p points to an old copy that is not being deleted. And unsafe { ::std::intrinsics::drop_in_place(&mut s); } unsafe { ::std::intrinsics::drop_in_place(&mut s); } works because it does not move s .

In general, there is no good way to either prevent LLVM from moving values ​​on the stack, or to zero after moving them, so you should never put cryptographically sensitive data on the stack. Instead, you should Box any sensitive data like say

 #[derive(Debug, Default)] pub struct AnotherSecret(Box<[u8; 32]>); impl Drop for AnotherSecret { fn drop(&mut self) { *self.0 = [0u8; 32]; } } 

There should be no problem with Mutex or RwLock , as they can safely leave traces on the stack when they drop ed.

+4
source

Yes : side effects.

Optimizers in general and LLVM in particular work in accordance with the as-if rule: you create a program that has certain observable behavior, and the optimizer is given free ownership to create any binary file if it has the same observable behavior.

Note that the burden of proof is in the compiler. That is, when calling an opaque function (for example, defined in another library), the compiler must assume that it may have side effects. In addition, side effects cannot be reordered, as this can alter the observed behavior.

In the case of Mutex , for example, getting and releasing Mutex usually opaque to the compiler (this requires an OS call), so it is considered as a side effect. I would expect compilers to not discuss them.

On the other hand, your Secret is a difficult case: in most cases there is no side effect when canceling secrecy (resetting the released memory is a dead record that needs to be optimized), so you need to get out of the way to make sure this happens .. making sure the compiler has side effects using volatile write.

+3
source

Source: https://habr.com/ru/post/1262550/


All Articles