Sécurité des applications Rust : top 10 des vulnérabilités pour débutants
\n\nRust est considéré comme l'un des langages de programmation les plus sûrs grâce à son système de possession (ownership) et son typage strict. Cependant, même en Rust, des erreurs peuvent survenir, notamment lors de l'utilisation de blocs unsafe ou d'une mauvaise gestion des bibliothèques externes. Dans cet article, nous analyserons le top 10 des vulnérabilités rencontrées par les développeurs débutants en Rust et montrerons comment les éviter.
1. Utilisation non sécurisée du code unsafe
\n\nLa principale caractéristique de Rust est la sécurité mémoire sans ramasse-miettes. Cependant, le mot-clé unsafe désactive certaines vérifications du compilateur. Les débutants en abusent souvent sans en comprendre les conséquences.
// Dangereux : déréférencement d'un pointeur brut sans vérification\nfn dangerous() {\n let ptr = 0x12345 as *const i32;\n unsafe {\n println!("Valeur : {}", *ptr); // Peut provoquer un segfault\n }\n}\n\n// Sécurisé : utilisation de méthodes vérifiées\nfn safe() {\n let x = 42;\n let ptr = &x as *const i32;\n unsafe {\n if !ptr.is_null() {\n println!("Valeur : {}", *ptr);\n }\n }\n}\n\nRecommandation : Utilisez unsafe uniquement lorsque c'est vraiment nécessaire, et vérifiez toujours les pointeurs pour null.
2. Fuites mémoire via Box::leak et cycles Rc
\n\nBien que Rust prévienne la plupart des fuites mémoire, les références cycliques avec Rc ou RefCell peuvent en provoquer. Box::leak est également dangereux car il "oublie" intentionnellement la mémoire.
use std::rc::Rc;\nuse std::cell::RefCell;\n\n#[derive(Debug)]\nstruct Node {\n value: i32,\n next: Option<Rc<RefCell<Node>>>,\n}\n\nfn create_cycle() {\n let a = Rc::new(RefCell::new(Node { value: 1, next: None }));\n let b = Rc::new(RefCell::new(Node { value: 2, next: None }));\n \n // Création d'un cycle : a -> b -> a\n a.borrow_mut().next = Some(b.clone());\n b.borrow_mut().next = Some(a.clone());\n // La mémoire ne sera jamais libérée !\n}\n\nSolution : Utilisez Weak<T> pour briser les références cycliques ou repensez l'architecture.
3. Courses aux données (Data Races) dans le code multithreadé
\n\nRust garantit l'absence de courses aux données dans le code sécurisé, mais une mauvaise utilisation de unsafe ou des traits Send/Sync peut poser problème.
use std::thread;\nuse std::sync::Arc;\nuse std::sync::Mutex;\n\n// Dangereux : partage d'un état mutable sans synchronisation\nfn data_race_example() {\n let mut counter = 0;\n let mut handles = vec![];\n \n for _ in 0..10 {\n handles.push(thread::spawn(|| {\n counter += 1; // Erreur de compilation : cannot borrow\n }));\n }\n}\n\n// Sécurisé : utilisation de Mutex\nfn safe_mutex() {\n let counter = Arc::new(Mutex::new(0));\n let mut handles = vec![];\n \n for _ in 0..10 {\n let counter = Arc::clone(&counter);\n handles.push(thread::spawn(move || {\n let mut num = counter.lock().unwrap();\n *num += 1;\n }));\n }\n}\n\n4. Débordement de tampon (Buffer Overflow)
\n\nEn Rust, le débordement de tampon est peu probable dans le code sécurisé, mais possible lors de l'utilisation de unsafe ou de l'interface FFI (Foreign Function Interface).
// Dangereux : copie non sécurisée dans un tampon\nfn unsafe_copy(src: &[u8], dst: &mut [u8]) {\n unsafe {\n std::ptr::copy_nonoverlapping(\n src.as_ptr(),\n dst.as_mut_ptr(),\n src.len() // Si src.len() > dst.len() - débordement !\n );\n }\n}\n\n// Sécurisé : utilisation de slice::copy_from_slice\nfn safe_copy(src: &[u8], dst: &mut [u8]) {\n if src.len() <= dst.len