2017-01-10 46 views
5

Zauważyłem, że mem::drop nie trzeba uruchamiać w pobliżu miejsca, w którym zostanie wywołany, co prawdopodobnie skutkuje przetrzymaniem strażników podczas kosztownych obliczeń. Jak mogę kontrolować, kiedy zostanie wywołana drop?Czy istnieje jakiś bezpieczny sposób na zapewnienie, że nastąpi dowolny zrzut przed kosztownym obliczeniem?

Jako prosty przykład, wykonałem następujący test zerującego spadku materiału kryptograficznego przy użyciu unsafe { ::std::intrinsics::drop_in_place(&mut s); } zamiast po prostu ::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); 
    } 
} 

Ten test nie powiedzie się, jeśli mogę użyć ::std::mem::drop(s) lub nawet

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

jest to oczywiście dobrze używać drop_in_place na test, że bufor zostanie wyzerowany, ale będę się martwić, że nazywając drop_in_place na Mutex lub RwLock ochrona może spowodować użycie po bezpłatnym.

Te dwie osłony mogłyby być może być traktowane z takim podejściem:

#[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

Podniosłem to jako problem z kompilatorem rdzy https://github.com/rust-lang/rfcs/issues/1850 po stwierdzeniu, że procesowanie potoku CPU itp. Nie było sprawcami. –

+0

Odpowiedź: nie należy umieszczać tajnego materiału klucza na stosie, ponieważ wszystko na stosie zostanie skopiowane. –

+0

może dodać [ogrodzenie] (https://doc.rust-lang.org/std/sync/atomic/fn.fence.html). AIUI, które powinno zapobiegać zmianie kolejności. – the8472

Odpowiedz

3

Tak: skutki uboczne.

Optymalizatory w ogóle, a LLVM w szczególności, działają zgodnie z zasadą "jak to": budujesz program, który ma określone obserwowalne zachowanie, a optymalizator otrzymuje wolne panowanie, aby produkować wszystko, czego chce, pod warunkiem, że ma to samo obserwowalne zachowanie.

Należy zauważyć, że ciężar dowodu spoczywa na kompilatorze. Oznacza to, że podczas wywoływania funkcji nieprzezroczystej (na przykład zdefiniowanej w innej bibliotece) kompilator ma, aby założyć, że może powodować działania niepożądane. Ponadto skutków ubocznych nie można zmienić, ponieważ może to zmienić obserwowalne zachowanie.

Na przykład w przypadku Mutex, na przykład uzyskanie i zwolnienie Mutex jest zasadniczo nieprzejrzyste dla kompilatora (wymaga wywołania systemu operacyjnego), więc jest postrzegane jako efekt uboczny. Spodziewam się, że kompilatory nie będą się nimi bawić.

Z drugiej strony, Twój Secret jest trudnym przypadkiem: przez większość czasu nie ma efektu ubocznego w upuszczeniu sekretu (wyzerowanie pamięci, która ma być zwolniona, jest martwym zapisem, aby zostać zoptymalizowanym) , dlatego musisz zejść z drogi, aby się upewnić, że to nastąpi ... poprzez przekonanie kompilatora, że ​​istnieją efekty uboczne przy użyciu zapisu volatile.

+0

Przedstawiony kod ** nie ** używa jednak ulotnych zapisów. – Shepmaster

+0

Tak, dlatego mówię, że OP robi wszystko, aby przekonać kompilator, że są efekty uboczne. –

+0

Ale dlaczego zmienne zapisy nie działałyby z opadem? – Shepmaster

4

Odpowiedź z https://github.com/rust-lang/rfcs/issues/1850:

W trybie debugowania, każde wezwanie do ::std::mem::drop(s) fizycznie porusza s na stosie, więc p punkty do starej kopii, które nie zostanie skasowane. I unsafe { ::std::intrinsics::drop_in_place(&mut s); } działa, ponieważ nie porusza się s.

Ogólnie rzecz biorąc, nie ma dobrego sposobu, aby zapobiec przenoszeniu wartości LLVM na stosie lub zerowaniu po przeniesieniu, więc nigdy nie należy umieszczać na stosie danych wrażliwych na kryptografię.Zamiast tego trzeba Box żadnych poufnych danych, takich jak powiedzieć

#[derive(Debug, Default)] 
pub struct AnotherSecret(Box<[u8; 32]>); 

impl Drop for AnotherSecret { 
    fn drop(&mut self) { 
     *self.0 = [0u8; 32]; 
    } 
} 

Nie powinno być żadnego problemu z Mutex lub RwLock ponieważ mogą bezpiecznie zostawić resztę na stosie, gdy są one drop ed.