Para mantener la integridad y la seguridad de un proyecto en Rust, es necesario ir más allá de la validación estándar del compilador. El ecosistema ofrece tres herramientas críticas: cargo fix para la evolución del código, cargo audit para la seguridad de dependencias y Miri para la validación de la memoria en bloques unsafe.
cargo fix es una herramienta de refactorización automática que aplica las sugerencias del compilador y de Clippy directamente en tus archivos fuente. Es fundamental durante las migraciones de edición (usando cargo fix --edition) para actualizar la sintaxis de versiones antiguas a la actual de forma automática. También es extremadamente útil con el flag --broken-code, que intenta corregir errores que impiden la compilación. Ignorar estas sugerencias puede llevar a una deuda técnica creciente o a la persistencia de patrones obsoletos.
cargo audit se enfoca en la seguridad de la cadena de suministro. Escanea tu archivo Cargo.lock y lo compara con la base de datos de vulnerabilidades de RustSec (RustSec Advisory Database). Su objetivo es detectar si alguna de tus dependencias tiene fallos de seguridad conocidos. Es una práctica esencial en entornos de Integración Continua (CI) para evitar que dependencias vulnerables lleguen a producción. Complementariamente, cargo update se utiliza para actualizar las versiones de las dependencias al rango permitido por SemVer, manteniendo el ecosistema al día.
Miri es un intérprete de Rust que permite ejecutar código en una máquina virtual para detectar Comportamiento Indefinido (Undefined Behavior o UB). A diferencia de un ejecutable estándar, Miri rastrea el estado de cada byte de memoria, lo que le permite detectar: acceso a memoria no inicializada, desbordamientos de límites (out-of-bounds), violaciones de aliasing (basadas en el modelo Stacked Borrows) y condiciones de carrera en la memoria. Debido a que es un intérprete, es significativamente más lento que una ejecución normal y no puede ejecutar todo el código (como llamadas al sistema o código de FFI), por lo que se usa principalmente en pruebas unitarias mediante cargo +nightly miri test.
use std::mem::MaybeUninit;
fn main() {
// Este código es sintácticamente correcto y compila sin errores,
// pero contiene comportamientos indefinidos (UB) que Miri detectará.
let mut datos = [1, 2, 3];
let puntero = datos.as_mut_ptr();
unsafe {
// 1. Error: Fuera de límites (Out of bounds)
// Accedemos a una posición que no pertenece al array.
let _fuera_de_limite = *puntero.add(5);
// 2. Error: Memoria no inicializada
// Intentamos leer un valor de una variable que no tiene datos válidos.
let no_inicializado: MaybeUninit<u8> = MaybeUninit::uninit();
let _valor_basura = no_inicializado.assume_init();
// 3. Error: Violación de aliasing (Stacked Borrows)
// Creamos una referencia mutable y luego modificamos la memoria
// mediante un puntero crudo mientras la referencia sigue activa.
let _ref_mut = &mut datos[0];
*puntero = 10;
}
println!("Ejecución terminada");
}
Análisis del código
let mut datos = [1, 2, 3];: Se inicializa un arreglo de tres enteros de 32 bits llamadodatos.let puntero = datos.as_mut_ptr();: Se obtiene un puntero crudo (*mut i32) hacia el primer elemento dedatos.unsafe { ... }: Bloque necesario para realizar operaciones de punteros y desreferenciación manual.let _fuera_de_limite = *puntero.add(5);: Utiliza el métodoadd(5)sobrepunteropara intentar acceder a una posición de memoria fuera del rango dedatos. Miri lanzará un error de “pointer out of bounds”.let no_inicializado: MaybeUninit<u8> = MaybeUninit::uninit();: Crea una variable de tipoMaybeUninit<u8>que no tiene un valor asignado en memoria.let _valor_basura = no_inicializado.assume_init();: Llama aassume_init(), lo cual le dice al compilador que confíe en que la memoria está inicializada. Como no lo está, Miri detectará un uso de “uninitialized memory”.let _ref_mut = &mut datos[0];: Crea una referencia mutable exclusiva sobre el primer elemento dedatos.*puntero = 10;: Modifica la memoria a través de un puntero crudo mientras_ref_mutsigue viva. Esto viola las reglas de aliasing (una referencia mutable no puede coexistir con cualquier otra forma de acceso a la misma memoria), lo cual es detectado por el modelo de Stacked Borrows de Miri.
N° 115