Introducción: Mitos y realidad de la seguridad en Rust
A menudo se dice que Rust es un lenguaje "seguro" y "confiable", y en gran medida es cierto. Gracias a su sistema de ownership (propiedad) y a su estricto compilador, Rust elimina clases enteras de vulnerabilidades típicas de C y C++ (como el desbordamiento de búfer o los punteros colgantes). Sin embargo, esto no significa que las aplicaciones en Rust sean invulnerables. En la práctica, los desarrolladores se enfrentan a errores de lógica, uso incorrecto de unsafe y vulnerabilidades a nivel de arquitectura.
En este artículo, analizaremos las 10 vulnerabilidades más comunes en aplicaciones Rust. Aprenderá cómo surgen, cómo detectarlas y cómo proteger su código. Este material será útil tanto para principiantes como para desarrolladores experimentados.
1. Errores en código unsafe
El "talón de Aquiles" más conocido de Rust son los bloques unsafe. Permiten realizar operaciones que el compilador no puede verificar: desreferenciar punteros crudos, llamar a funciones externas (FFI), modificar variables estáticas. Si se comete un error en unsafe, toda la seguridad de Rust se desmorona.
Ejemplo de vulnerabilidad
fn unsafe_deref(ptr: *const i32) -> i32 { unsafe { *ptr } // Si ptr es nulo o apunta a memoria liberada — UBCómo protegerse
- Minimice el uso de
unsafe. Extráigalo a módulos pequeños y cuidadosamente probados. - Use
#[deny(unsafe_code)]en los crates donde sea posible. - Verifique que los punteros sean válidos antes de desreferenciarlos.
2. Condiciones de carrera (Data Races) en código multihilo
Rust garantiza la ausencia de data races en código seguro gracias a sus reglas de ownership y borrowing. Sin embargo, al usar unsafe o trabajar incorrectamente con Mutex/RwLock, las condiciones de carrera son posibles. Son especialmente peligrosas las situaciones con Cell/RefCell en un entorno multihilo.
Ejemplo de vulnerabilidad
use std::cell::RefCell;use std::thread;
fn main() { let data = RefCell::new(0); thread::spawn(move || { *data.borrow_mut() += 1; // Pánico: RefCell no es Sync });}Cómo protegerse
- Use
Arc<Mutex<T>>oArc<RwLock<T>>para datos compartidos. - Evite
unsafeal trabajar con hilos a menos que sea estrictamente necesario. - Aplique el analizador estático
clippyymiripara detectar comportamiento indefinido.
3. Fugas de memoria (Memory Leaks)
Aunque Rust previene fugas de memoria en la mayoría de los casos, estas siguen siendo posibles. Por ejemplo, al crear referencias cíclicas con Rc/RefCell o al olvidar llamar al destructor en código unsafe. Las fugas de memoria en servicios de larga duración pueden provocar el agotamiento de los recursos.
Ejemplo de vulnerabilidad
use std::rc::Rc;use std::cell::RefCell;
struct Node { next: Option<Rc<RefCell<Node>>>,}
fn main() { let a = Rc::new(RefCell::new(Node { next: None })); let b = Rc::new(RefCell::new(Node { next: Some(a.clone()) })); a.borrow_mut().next = Some(b); // Referencia cíclica — fuga}Cómo protegerse
- Use
Weakpara romper referencias cíclicas. - Para aplicaciones de larga duración, emplee perfiladores de memoria (por ejemplo,
valgrindoheaptrack). - En código
unsafe, verifique siempre que la memoria se libere correctamente.
4. Manejo incorrecto de errores
Rust fomenta el manejo explícito de errores a través de Result y Option. Sin embargo, los desarrolladores a menudo ignoran los errores usando .unwrap() o .expect(). En producción, esto puede provocar