Introduction : Mythes et réalité de la sécurité de Rust
Le langage Rust est souvent qualifié de "sûr" et "fiable", ce qui est en grande partie vrai. Grâce à son système de possession (ownership) et à son compilateur strict, Rust élimine des classes entières de vulnérabilités typiques de C et C++ (par exemple, les débordements de tampon ou les pointeurs pendants). Cependant, cela ne signifie pas que les applications Rust sont invulnérables. En pratique, les développeurs rencontrent des erreurs de logique, une mauvaise utilisation de unsafe et des vulnérabilités au niveau architectural.
Dans cet article, nous allons analyser le top 10 des vulnérabilités les plus fréquentes dans les applications Rust. Vous apprendrez comment elles surviennent, comment les détecter et comment sécuriser votre code. Ce contenu sera utile aussi bien aux débutants qu'aux développeurs expérimentés.
1. Erreurs dans le code unsafe
Le "talon d'Achille" le plus connu de Rust est les blocs unsafe. Ils permettent d'effectuer des opérations que le compilateur ne peut pas vérifier : déréférencement de pointeurs bruts, appel de fonctions externes (FFI), modification de variables statiques. Si une erreur est commise dans unsafe, toute la sécurité de Rust s'effondre.
Exemple de vulnérabilité
fn unsafe_deref(ptr: *const i32) -> i32 { unsafe { *ptr } // Si ptr est nul ou pointe vers de la mémoire libérée — UB}
Comment se protéger
- Minimisez l'utilisation de
unsafe. Placez-le dans des modules petits et minutieusement testés. - Utilisez
#[deny(unsafe_code)]dans les crates lorsque c'est possible. - Vérifiez la validité des pointeurs avant de les déréférencer.
2. Courses de données (Data Races) dans le code multithreadé
Rust garantit l'absence de courses de données dans le code sûr grâce aux règles de possession et d'emprunt. Cependant, lors de l'utilisation de unsafe ou d'une mauvaise manipulation de Mutex/RwLock, des courses sont possibles. Les situations avec Cell/RefCell dans un environnement multithreadé sont particulièrement dangereuses.
Exemple de vulnérabilité
use std::cell::RefCell;use std::thread;
fn main() { let data = RefCell::new(0); thread::spawn(move || { *data.borrow_mut() += 1; // Panique : RefCell n'est pas Sync });}
Comment se protéger
- Utilisez
Arc<Mutex<T>>ouArc<RwLock<T>>pour les données partagées. - Évitez
unsafelors du travail avec les threads sauf en cas de nécessité absolue. - Appliquez l'analyseur statique
clippyetmiripour détecter les comportements indéfinis.
3. Fuites de mémoire (Memory Leaks)
Bien que Rust prévienne les fuites dans la plupart des cas, elles sont toujours possibles. Par exemple, lors de la création de références cycliques avec Rc/RefCell ou en oubliant d'appeler le destructeur dans du code unsafe. Les fuites de mémoire dans les services à longue durée de vie peuvent entraîner un épuisement des ressources.
Exemple de vulnérabilité
use std::rc::Rc;use std::cell::RefCell;
struct Node { next: Option<Rc<RefCell<Node>>>,}
fn main() { let a = Rc::new(RefCell::new(Node { next: None })); let b = Rc::new(RefCell::new(Node { next: Some(a.clone()) })); a.borrow_mut().next = Some(b); // Référence cyclique — fuite}
Comment se protéger
- Utilisez
Weakpour briser les références cycliques. - Pour les applications à longue durée de vie, utilisez des profileurs de mémoire (par exemple,
valgrindouheaptrack). - Dans le code
unsafe, vérifiez toujours que la mémoire est libérée correctement.
4. Gestion incorrecte des erreurs
Rust encourage la gestion explicite des erreurs via Result et Option. Cependant, les développeurs ignorent souvent les erreurs en utilisant .unwrap() ou .expect(). En production, cela peut en