Sécurité des applications Rust : top 10 des vulnérabilités en pratique
Rust se positionne comme un langage système sécurisé, capable de prévenir des classes entières d'erreurs typiques de C et C++. Cependant, cela ne signifie pas que les applications Rust sont invulnérables. Le système de possession (ownership) et le borrow checker protègent contre de nombreux problèmes, mais le code unsafe, les erreurs logiques et les dépendances externes ouvrent de nouveaux vecteurs d'attaque. Dans cet article, nous analyserons les 10 vulnérabilités les plus critiques rencontrées par les développeurs Rust — avec des exemples de code réels et des conseils pratiques pour les corriger.
Ce contenu sera utile aussi bien aux débutants qu'aux ingénieurs expérimentés souhaitant approfondir leurs connaissances en sécurité dans l'écosystème Rust.
1. Utilisation dangereuse de unsafe
Le mot-clé unsafe est la source la plus fréquente de vulnérabilités en Rust. Il désactive les vérifications du borrow checker et permet de travailler avec des pointeurs bruts, mais la responsabilité de la correction incombe entièrement au développeur.
// Code vulnérable : déréférencement d'un pointeur nulfn dangerous() { let ptr: *const i32 = std::ptr::null(); unsafe { println!("{}", *ptr); // UB ! Segfault }}Solution : minimisez l'utilisation de unsafe. Isolez les opérations dangereuses dans des modules séparés avec des tests rigoureux et une documentation des invariants. Utilisez NonNull<T> au lieu de pointeurs bruts.
2. Courses aux données (Data Races)
Bien que le borrow checker empêche les courses dans le code sécurisé, unsafe et une mauvaise utilisation des primitives de synchronisation peuvent entraîner des conditions de course.
// Code vulnérable : course aux données via unsafestatic mut COUNTER: i32 = 0;
fn increment() { unsafe { COUNTER += 1; // Opération non atomique }}Solution : utilisez AtomicI32, Mutex ou RwLock. Dans le code sécurisé Rust, les courses aux données sont impossibles — faites confiance au compilateur.
3. Débordement d'entiers (Integer Overflow)
En mode debug, Rust panique en cas de débordement, mais en mode release, un wrapping (dépassement cyclique) se produit par défaut, ce qui peut entraîner un comportement inattendu.
// Vulnérabilité potentielle : logique de débordementfn allocate_buffer(size: usize) { let header_size = 64; let total = size + header_size; // Peut déborder let buf = vec![0u8; total]; // Panique ou petit tampon}Solution : utilisez les méthodes checked_add(), saturating_add() ou une vérification explicite. Pour les calculs critiques, utilisez la crate overflow-checks.
4. Fuites de mémoire dans le code unsafe
Rust garantit l'absence de fuites dans le code sécurisé, mais unsafe peut violer ce principe. Par exemple, une mauvaise gestion des pointeurs bruts ou des références cycliques avec Rc.
// Fuite mémoire : oubli d'appeler dropfn leak_memory() { let ptr = Box::into_raw(Box::new(42)); // ptr n'est pas libéré — fuite}Solution : utilisez Box::from_raw() pour une libération correcte. Pour les références cycliques, utilisez Weak ou Arc.
5. Appels FFI dangereux
Les appels aux fonctions C via FFI (Foreign Function Interface) sont l'une des principales sources de vulnérabilités. Rust ne peut pas contrôler la sécurité du code externe.
// FFI dangereux : transmission d'un pointeur incorrectextern "C" { fn c_free(ptr: *mut c_void);}
fn free_wrapper(ptr: *mut c_void) { unsafe { c_free(ptr); } // Double libération ?}Solution : utilisez des wrappers sécurisés, vérifiez les limites des tampons, appliquez std::ffi::CStr pour les chaînes