2017-01-10 3 views
5

J'ai trouvé que mem::drop ne fonctionne pas nécessaire près de là où il est appelé, dont les résultats probables dans Mutex ou RwLock gardes détenus pendant des calculs coûteux. Comment puis-je contrôler quand drop est appelé?Existe-t-il un moyen sûr d'assurer une chute arbitraire avant un calcul coûteux?

Comme un exemple simple, j'ai fait le test suivant pour une réduction à zéro du matériel cryptographique en utilisant unsafe { ::std::intrinsics::drop_in_place(&mut s); } au lieu de simplement ::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); 
    } 
} 

Ce test échoue si j'utilise ::std::mem::drop(s) ou même

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

Il est évidemment bon d'utiliser drop_in_place pour un test qu'un tampon se remis à zéro, mais je me inquiéterais que l'appel drop_in_place sur un Mutex ou RwLock garde pourrait entraîner une utilisation après libre.

Ces deux gardiens pourraient peut-être être manipulés avec cette approche:

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

J'ai soulevé cela comme un problème avec le compilateur de rouille https://github.com/rust-lang/rfcs/issues/1850 après avoir identifié que la mise en pipeline de processeurs, etc., n'étaient pas les coupables. –

+0

La réponse est de ne jamais mettre de matériel de clé secrète sur la pile car tout ce qui se trouve sur la pile est copié. –

+0

peut-être ajouter un [clôture] (https://doc.rust-lang.org/std/sync/atomic/fn.fence.html). AIUI qui devrait empêcher le réordonnancement. – the8472

Répondre

3

Oui: effets secondaires.

Les optimiseurs en général, et LLVM en particulier, fonctionnent sous la règle as-if: vous construisez un programme qui a un comportement observable spécifique, et l'optimiseur est libre de produire n'importe quel binaire tant qu'il a le même comportement observable.

Notez que la charge de la preuve incombe au compilateur. Autrement dit, lors de l'appel d'une fonction opaque (définie dans une autre bibliothèque, par exemple), le compilateur a pour supposer qu'il peut avoir des effets secondaires. De plus, les effets secondaires ne peuvent pas être réordonnés, car cela pourrait modifier le comportement observable. Par exemple, dans le cas de Mutex, l'acquisition et la libération du Mutex est généralement opaque pour le compilateur (elle nécessite un appel de système d'exploitation), ce qui est considéré comme un effet secondaire. Je m'attendrais à ce que les compilateurs ne se mêlent pas de ça. D'autre part, votre Secret est un casse-tête: la plupart du temps, il n'y a pas d'effet secondaire en abandonnant le secret (la mise à zéro de la mémoire à être libérée est une écriture morte, à optimiser) , c'est pourquoi vous devez sortir de votre chemin pour s'assurer qu'il se produit ... en convainquant le compilateur qu'il y a des effets secondaires en utilisant une écriture volatile.

+0

Le code présenté ** utilise ** des écritures volatiles cependant. – Shepmaster

+0

Oui, c'est pourquoi je dis que l'OP fait tout son possible pour convaincre le compilateur qu'il y a des effets secondaires. –

+0

Mais pourquoi les écritures volatiles ne fonctionneraient pas avec la baisse? – Shepmaster

4

Réponse de https://github.com/rust-lang/rfcs/issues/1850:

En mode débogage, tout appel à ::std::mem::drop(s) se déplace physiquement s sur la pile, donc p des points à une ancienne copie qui ne se sont pas effacées. Et unsafe { ::std::intrinsics::drop_in_place(&mut s); } fonctionne parce qu'il ne bouge pas s. En général, il n'existe aucun moyen d'empêcher LLVM de déplacer des valeurs sur la pile ou de les mettre à zéro après les avoir déplacées. Vous ne devez donc jamais placer de données cryptographiquement sensibles sur la pile.Au lieu de cela, vous devez Box des données sensibles, comme disent

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

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

Il devrait y avoir aucun problème avec Mutex ou RwLock, car ils peuvent laisser des résidus en toute sécurité sur la pile quand ils sont drop ed.