Segurança de aplicações Rust: top 10 vulnerabilidades na prática
Rust é posicionado como uma linguagem de sistema segura, capaz de prevenir classes inteiras de erros típicos de C e C++. No entanto, isso não significa que aplicações Rust sejam invulneráveis. O sistema de ownership e o borrow checker protegem contra muitos problemas, mas código unsafe, erros lógicos e dependências externas abrem novos vetores de ataque. Neste artigo, analisaremos as 10 vulnerabilidades mais críticas que desenvolvedores Rust enfrentam — com exemplos reais de código e dicas práticas para corrigi-las.
O material será útil tanto para iniciantes quanto para engenheiros experientes que desejam aprofundar seus conhecimentos sobre segurança no ecossistema Rust.
1. Uso inseguro de unsafe
A palavra-chave unsafe é a fonte mais comum de vulnerabilidades em Rust. Ela desativa as verificações do borrow checker e permite trabalhar com ponteiros brutos, mas a responsabilidade pela correção recai totalmente sobre o desenvolvedor.
// Código vulnerável: desreferenciamento de ponteiro nulofn dangerous() { let ptr: *const i32 = std::ptr::null(); unsafe { println!("{}", *ptr); // UB! Segfault }}Solução: minimize o uso de unsafe. Isole operações perigosas em módulos separados com testes rigorosos e documentação de invariantes. Use NonNull<T> em vez de ponteiros brutos.
2. Condições de corrida (Data Races)
Embora o borrow checker previna corridas em código seguro, o unsafe e o uso incorreto de primitivas de sincronização podem levar a condições de corrida.
// Código vulnerável: condição de corrida via unsafestatic mut COUNTER: i32 = 0;
fn increment() { unsafe { COUNTER += 1; // Operação não atômica }}Solução: use AtomicI32, Mutex ou RwLock. Em código Rust seguro, condições de corrida são impossíveis — confie no compilador.
3. Integer Overflow (estouro de inteiros)
No modo debug, Rust entra em pânico em caso de estouro, mas no modo release, o comportamento padrão é wrapping (transbordo cíclico), o que pode levar a comportamentos inesperados.
// Vulnerabilidade potencial: lógica de estourofn allocate_buffer(size: usize) { let header_size = 64; let total = size + header_size; // Pode estourar let buf = vec![0u8; total]; // Pânico ou buffer pequeno}Solução: use os métodos checked_add(), saturating_add() ou verificação explícita. Para cálculos críticos, utilize a crate overflow-checks.
4. Vazamentos de memória em código unsafe
Rust garante a ausência de vazamentos em código seguro, mas o unsafe pode violar esse princípio. Por exemplo, gerenciamento incorreto de ponteiros brutos ou referências cíclicas com Rc.
// Vazamento de memória: esqueceu de chamar dropfn leak_memory() { let ptr = Box::into_raw(Box::new(42)); // ptr não foi liberado — vazamento}Solução: use Box::from_raw() para liberação correta. Para referências cíclicas, utilize Weak ou Arc.
5. Chamadas FFI inseguras
Chamadas a funções C via FFI (Foreign Function Interface) são uma das principais fontes de vulnerabilidades. Rust não pode controlar a segurança do código externo.
// FFI perigoso: passagem de ponteiro inválidoextern "C" { fn c_free(ptr: *mut c_void);}
fn free_wrapper(ptr: *mut c_void) { unsafe { c_free(ptr); } // Liberação dupla?}Solução: use wrappers seguros, verifique os limites dos buffers, aplique std::ffi::CStr para str