Segurança de aplicações Rust: top 10 vulnerabilidades para iniciantes
Rust é posicionado como uma linguagem de sistema segura que previne classes inteiras de vulnerabilidades em tempo de compilação. No entanto, isso não significa que aplicações Rust sejam absolutamente invulneráveis. Desenvolvedores iniciantes frequentemente cometem erros que podem levar a sérios problemas de segurança. Neste artigo, vamos analisar as top 10 vulnerabilidades em aplicações Rust que todo iniciante deve conhecer.
1. Uso incorreto de código unsafe
Blocos unsafe são a causa mais comum de vulnerabilidades em Rust. Eles desativam as verificações de segurança, o que pode levar ao desreferenciamento de ponteiros nulos, condições de corrida e corrupção de memória.
// Exemplo perigosounsafe { let ptr = std::ptr::null::<i32>(); println!("{}", *ptr); // UB! Segmentation fault}
// Versão segurafn safe_access(ptr: *const i32) -> Option<i32> { if ptr.is_null() { return None; } unsafe { Some(*ptr) }}Recomendação: Minimize o uso de unsafe. Se for necessário, isole-o em funções pequenas e documente cuidadosamente os invariantes.
2. Condições de corrida através de Send e Sync
Rust protege contra condições de corrida através do sistema de ownership, mas a implementação incorreta das traits Send e Sync pode quebrar essa proteção.
use std::cell::UnsafeCell;use std::sync::Arc;
struct UnsafeCounter(UnsafeCell<u32>);
// Implementação perigosaunsafe 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 || { // Condição de corrida! unsafe { *c.0.get() += 1; } })); }}Recomendação: Use Arc<Mutex<T>> ou Arc<RwLock<T>> em vez de implementar sincronização manualmente.
3. Desserialização insegura de dados
Ao desserializar dados de fontes externas (JSON, YAML, bincode), um atacante pode fornecer um payload especialmente criado.
use serde::Deserialize;
#[derive(Deserialize)]struct Config { path: String, buffer_size: usize,}
fn load_config(data: &str) -> Result<Config, serde_json::Error> { // Vulnerabilidade potencial: números muito grandes let config: Config = serde_json::from_str(data)?; if config.buffer_size > 1_000_000 { return Err(serde_json::Error::custom("Buffer too large")); } Ok(config)}Recomendação: Sempre verifique os limites dos valores após a desserialização. Use #[serde(deserialize_with = "...")] para validadores personalizados.
4. Vazamentos de memória através de Box<dyn Error>
Retornar frequentemente Box<dyn Error> pode levar a vazamentos de memória, especialmente ao trabalhar com grandes volumes de dados.
// Abordagem problemáticafn process_data(data: &[u8]) -> Result<(), Box<dyn std::error::Error>> { // ... processamento Ok(())}
// Melhor abordagem#[derive(Debug)]enum AppError { InvalidData(String), IoError(std::io::Error),}
fn process_data_safe(data: &[u8]) -> Result<(), AppError> { // ... processamento Ok(())}Recomendação: Crie seus próprios tipos de erro usando thiserror ou anyhow para melhor desempenho e segurança.
5. Estouro de buffer em FFI
Ao interagir com código C através de FFI (Foreign Function Interface), é fácil cometer
```