Sécurité des applications Rust : top 10 des vulnérabilités pour débutants
Rust se positionne comme un langage système sécurisé qui prévient des classes entières de vulnérabilités dès la phase de compilation. Cependant, cela ne signifie pas que les applications Rust sont absolument invulnérables. Les développeurs débutants commettent souvent des erreurs qui peuvent entraîner de graves problèmes de sécurité. Dans cet article, nous allons analyser le top 10 des vulnérabilités dans les applications Rust que chaque débutant doit connaître.
1. Utilisation incorrecte du code unsafe
Les blocs unsafe sont la cause la plus fréquente de vulnérabilités en Rust. Ils désactivent les vérifications de sécurité, ce qui peut entraîner le déréférencement de pointeurs nuls, des courses de données et des corruptions de mémoire.
// Exemple dangereuxunsafe { let ptr = std::ptr::null::<i32>(); println!("{}", *ptr); // UB ! Segmentation fault}
// Variante sécuriséefn safe_access(ptr: *const i32) -> Option<i32> { if ptr.is_null() { return None; } unsafe { Some(*ptr) }}Recommandation : Minimisez l'utilisation de unsafe. S'il est nécessaire, isolez-le dans de petites fonctions et documentez soigneusement les invariants.
2. Courses de données via Send et Sync
Rust protège contre les courses de données via le système de possession, mais une implémentation incorrecte des traits Send et Sync peut compromettre cette protection.
use std::cell::UnsafeCell;use std::sync::Arc;
struct UnsafeCounter(UnsafeCell<u32>);
// Implémentation dangereuseunsafe impl Send for UnsafeCounter {}unsafe impl Sync for UnsafeCounter {}
fn main() { let counter = Arc::new(UnsafeCounter(UnsafeCell::new(0))); let mut handles = vec![]; for _ in 0..10 { let c = counter.clone(); handles.push(std::thread::spawn(move || { // Course de données ! unsafe { *c.0.get() += 1; } })); }}Recommandation : Utilisez Arc<Mutex<T>> ou Arc<RwLock<T>> au lieu d'une implémentation manuelle de la synchronisation.
3. Désérialisation non sécurisée des données
Lors de la désérialisation de données provenant de sources externes (JSON, YAML, bincode), un attaquant peut injecter un payload spécialement conçu.
use serde::Deserialize;
#[derive(Deserialize)]struct Config { path: String, buffer_size: usize,}
fn load_config(data: &str) -> Result<Config, serde_json::Error> { // Vulnérabilité potentielle : nombres trop grands let config: Config = serde_json::from_str(data)?; if config.buffer_size > 1_000_000 { return Err(serde_json::Error::custom("Buffer trop grand")); } Ok(config)}Recommandation : Vérifiez toujours les limites des valeurs après la désérialisation. Utilisez #[serde(deserialize_with = "...")] pour des validateurs personnalisés.
4. Fuites de mémoire via Box<dyn Error>
Le retour fréquent de Box<dyn Error> peut entraîner des fuites de mémoire, surtout lors du traitement de grands volumes de données.
// Approche problématiquefn process_data(data: &[u8]) -> Result<(), Box<dyn std::error::Error>> { // ... traitement Ok(())}
// Meilleure approche#[derive(Debug)]enum AppError { InvalidData(String), IoError(std::io::Error),}
fn process_data_safe(data: &[u8]) -> Result<(), AppError> { // ... traitement Ok(())}Recommandation : Créez vos propres types d'erreurs avec thiserror ou anyhow pour de meilleures performances et sécurité.
5. Dépassement de tampon dans FFI
Lors de l'interaction avec du code C via FFI (Foreign Function Interface), il est facile de commettre une