我发现mem::drop
没有必要在调用它的地方附近运行,这可能导致在昂贵的计算过程中保留Mutex
或RwLock
警卫。如何控制何时调用drop
?
作为一个简单的示例,我使用unsafe { ::std::intrinsics::drop_in_place(&mut s); }
而不是简单的::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);
}
}
如果我使用::std::mem::drop(s)
甚至
#[inline(never)]
pub fn drop_now<T>(_x: T) { }
使用drop_in_place
进行缓冲区归零的测试显然很好,但我担心在drop_in_place
或{{{{}}上调用Mutex
1}}后卫可能会在免费后使用。
可以用这种方法处理这两个警卫:
RwLock
答案 0 :(得分:4)
来自https://github.com/rust-lang/rfcs/issues/1850的答案:
在调试模式下,对::std::mem::drop(s)
的任何调用都会在堆栈上物理移动s
,因此p
指向不会被删除的旧副本。 unsafe { ::std::intrinsics::drop_in_place(&mut s); }
有效,因为它不会移动s
。
通常,没有好的方法可以阻止LLVM在堆栈上移动值,或者在移动它们之后将其置零,因此您绝不能将加密敏感数据放在堆栈上。相反,您必须Box
任何敏感数据,例如
#[derive(Debug, Default)]
pub struct AnotherSecret(Box<[u8; 32]>);
impl Drop for AnotherSecret {
fn drop(&mut self) {
*self.0 = [0u8; 32];
}
}
Mutex
或RwLock
应该没有任何问题,因为drop
时,它们可以安全地将残留物留在堆栈上。
答案 1 :(得分:3)
是:副作用。
一般的优化器,特别是LLVM,在as-if规则下运行:你构建一个具有特定可观察行为的程序,并且优化器被给予自由统治以产生它想要的任何二进制文件,只要它具有非常相同的可观察行为。
请注意,举证责任在编译器上。也就是说,当调用opaque函数(例如,在另一个库中定义)时,编译器具有以假设它可能具有副作用。此外,副作用无法重新排序,因为这可能会改变可观察的行为。
例如,在Mutex
的情况下,获取和释放Mutex
通常对编译器是不透明的(它需要OS调用),因此它被视为副作用。我希望编译器不要弄乱那些。
另一方面,你的Secret
是一个棘手的案例:大部分时间都没有放弃秘密的副作用(归零待释放的内存是一个死写,是优化了),这就是为什么你需要尽力确保它发生...通过说服编译器使用volatile
写入产生副作用。