有没有什么安全的方法可以确保在一些昂贵的计算之前发生任意丢弃?

时间:2017-01-10 03:01:25

标签: multithreading rust raii

我发现mem::drop没有必要在调用它的地方附近运行,这可能导致在昂贵的计算过程中保留MutexRwLock警卫。如何控制何时调用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

2 个答案:

答案 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];
    }
}

MutexRwLock应该没有任何问题,因为drop时,它们可以安全地将残留物留在堆栈上。

答案 1 :(得分:3)

:副作用。

一般的优化器,特别是LLVM,在as-if规则下运行:你构建一个具有特定可观察行为的程序,并且优化器被给予自由统治以产生它想要的任何二进制文件,只要它具有非常相同的可观察行为。

请注意,举证责任在编译器上。也就是说,当调用opaque函数(例如,在另一个库中定义)时,编译器具有以假设它可能具有副作用。此外,副作用无法重新排序,因为这可能会改变可观察的行为。

例如,在Mutex的情况下,获取和释放Mutex通常对编译器是不透明的(它需要OS调用),因此它被视为副作用。我希望编译器不要弄乱那些。

另一方面,你的Secret是一个棘手的案例:大部分时间都没有放弃秘密的副作用(归零待释放的内存是一个死写,是优化了),这就是为什么你需要尽力确保它发生...通过说服编译器使用volatile写入产生副作用。