Sécurité des applications Rust : top 10 des vulnérabilités et méthodes de protection pratiques
Le langage Rust est à juste titre considéré comme l'un des langages de programmation système les plus sûrs. Son système de possession (ownership), ses mécanismes d'emprunt (borrowing) et de durée de vie (lifetimes) à l'étape de compilation empêchent des classes entières de vulnérabilités liées à la gestion de la mémoire. Cependant, comme le montre la pratique, la sécurité de Rust n'est pas absolue. Les erreurs de conception, l'utilisation incorrecte du code unsafe et les bugs logiques peuvent entraîner de graves vulnérabilités.
Dans cet article, nous allons analyser le top 10 des vulnérabilités des applications Rust que chaque développeur devrait connaître. Vous découvrirez non seulement les causes de leur apparition, mais vous obtiendrez également des exemples de code concrets et des méthodes de protection.
1. Utilisation incorrecte du code unsafe
unsafe est l'outil le plus puissant, mais aussi le plus dangereux de Rust. Il désactive les vérifications du compilateur, permettant de déréférencer des pointeurs bruts, d'appeler des fonctions unsafe et d'accéder à des variables statiques mutables. Selon les études, plus de 70 % de toutes les vulnérabilités critiques dans les projets Rust sont liées au code unsafe.
Exemple de vulnérabilité : déréférencement d'un pointeur nul dans un bloc unsafe.
fn dangerous() { let ptr: *const i32 = std::ptr::null(); unsafe { println!("{}", *ptr); // Comportement indéfini ! }}Comment se protéger :
- Minimisez l'utilisation de
unsafe. Placez-le dans de petites fonctions soigneusement testées. - Vérifiez toujours que les pointeurs ne sont pas nuls avant de les déréférencer.
- Utilisez des outils comme
Miripour détecter les comportements indéfinis.
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 à son système de types. Cependant, lors de l'utilisation de unsafe ou d'une synchronisation incorrecte, les courses restent possibles. Les courses sont particulièrement dangereuses lors du travail avec des statiques mutables globales.
Exemple de vulnérabilité : accès à une variable statique mutable sans synchronisation.
static mut COUNTER: u32 = 0;
fn increment() { unsafe { COUNTER += 1; // Course de données dans un environnement multithreadé }}Comment se protéger :
- Utilisez
Mutex,RwLockou des types atomiques (AtomicU32). - Évitez les états mutables globaux. Privilégiez le transfert de possession via des canaux (
mpsc). - Appliquez les traits
SendetSyncde manière réfléchie.
3. Débordement de tampon (Buffer Overflow) en Rust
En Rust sûr, le débordement de tampon est pratiquement impossible — le compilateur vérifie les limites des tableaux. Mais lors du travail avec des structures de bas niveau dans unsafe ou lors de l'utilisation de FFI (Foreign Function Interface), le débordement devient une menace réelle.
Exemple de vulnérabilité : copie de données au-delà des limites du tampon via FFI.
use std::ffi::CString;
extern "C" { fn unsafe_copy(dest: *mut u8, src: *const u8, len: usize);}
fn copy_vulnerable(data: &[u8]) { let dest = CString::new("").unwrap(); unsafe { unsafe_copy(dest.into_raw(), data.as_ptr(), data.len()); // Débordement ! }}Comment se protéger :
- Vérifiez toujours la longueur du tampon côté Rust avant d'appeler des fonctions C.
- Utilisez des crates d'encapsulation pour les bibliothèques C qui garantissent la sécurité.
- Appliquez des sanitizers (AddressSanitizer) lors des tests.
4. Fuites de mémoire (Memory Leaks)
Bien que Rust n'ait pas de ramasse-miettes, son système de posse