2017-01-10 14 views
5

Я обнаружил, что 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 или RwLock гвардия может привести к использованию после бесплатного.

Эти два охранника могли возможно обращаться с этим подходом:

#[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); } 
} 
+0

Я понял это как проблему с компилятором ржавчины https://github.com/rust-lang/rfcs/issues/1850 после определения того, что конвейерная обработка ЦП и т. Д. Не были виновниками. –

+0

Ответ: никогда не кладите секретный материал в стек, потому что что-то в стеке копируется. –

+0

возможно добавить [забор] (https://doc.rust-lang.org/std/sync/atomic/fn.fence.html). AIUI, который должен предотвратить переупорядочение. – the8472

ответ

3

Да: побочные эффекты.

Оптимизаторы в целом и LLVM, в частности, работают под правилом as-if: вы создаете программу, которая имеет определенное наблюдаемое поведение, а оптимизатору предоставляется свободное владение, чтобы производить любые бинарные файлы, которые он хочет, такое же наблюдаемое поведение.

Обратите внимание, что бремя доказательства находится на компиляторе. То есть при вызове непрозрачной функции (например, определенной в другой библиотеке) компилятор имеет, чтобы предположить, что он может иметь побочные эффекты. Кроме того, побочные эффекты не могут быть переупорядочены, так как это может изменить наблюдаемое поведение.

В случае Mutex, например, получение и освобождение Mutex, как правило, непрозрачно для компилятора (для этого требуется вызов ОС), поэтому он рассматривается как побочный эффект. Я бы ожидал, что компиляторы не будут возиться с ними.

С другой стороны, ваш Secret - это сложный случай: в большинстве случаев нет побочного эффекта при отмене секретности (обнуление выпущенной памяти является мертвой записью, которая должна быть оптимизирована) , поэтому вам нужно уйти с пути, чтобы убедиться, что это происходит ... убедившись в компиляторе, что есть побочные эффекты, используя запись volatile.

+0

Представленный код ** делает ** использование volatile пишет, хотя. – Shepmaster

+0

Да, именно поэтому я говорю, что OP изо всех сил убеждает компилятор в наличии побочных эффектов. –

+0

Но почему волатильные записи не работают с падением? – Shepmaster

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 ред.