flawopen.com/Seguridad de memoria/Rust unsafe
¿El bloque unsafe de Rust sigue siendo seguro para la memoria?
unsafe no desactiva las comprobaciones de Rust. Permite operaciones concretas cuyas condiciones de seguridad el compilador no puede demostrar; el programador debe cumplir esas obligaciones. La consecuencia clave es que una abstracción unsafe no sound puede permitir que llamadas safe comunes provoquen comportamiento indefinido (UB), aunque nunca escriban unsafe.
Términos clave
- Operación unsafe:
- Operación cuyas condiciones de seguridad no puede demostrar el compilador, como desreferenciar un puntero raw.
- Abstracción sound:
- API safe cuyos llamadores safe no pueden provocar comportamiento indefinido.
- size_hint():
- Estimación del tamaño de un iterador para optimizar; no es un recuento exacto ni una prueba de seguridad de memoria.
- Comportamiento indefinido (UB):
- Comportamiento que Rust no define; el código unsafe debe impedir que lo provoquen los llamadores safe.
Cómo falla una abstracción safe no sound
Una API safe recibe un iterador
El llamador pasa un iterador normal a una función sin precondiciones unsafe.
La implementación reserva según una estimación
La función reserva solo el límite inferior indicado, que puede ser menor que la cantidad de elementos que produzca el iterador.
Las escrituras sin comprobación superan la capacidad
La implementación escribe cada elemento mediante un puntero raw sin ampliar la asignación ni comprobar la capacidad en cada paso.
El uso safe llega a UB
Si el iterador produce más elementos de los que caben, una llamada safe puede escribir fuera de los límites. Use Vec::extend u otra implementación que crezca de forma segura.
// UNSOUND: reserve uses only the lower hint, // then unchecked writes consume every item. fn append_unsound(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { let (lower, _) = iter.size_hint(); dst.reserve(lower); let start = dst.len(); let mut written = 0; unsafe { let out = dst.as_mut_ptr().add(start); for item in iter { // UB once writes exceed spare capacity. out.add(written).write(item); written += 1; } dst.set_len(start + written); } }
// SOUND: Vec::extend handles capacity // growth and length updates safely. fn append(dst: &mut Vec<u8>, iter: impl Iterator<Item = u8>) { dst.extend(iter); }
Qué garantiza realmente Rust
La garantía precisa es: Rust safe no puede causar comportamiento indefinido si el código unsafe del que depende es sound. La Rust Reference define soundness en el límite: el código unsafe debe impedir que los clientes safe provoquen UB. El compilador no verifica todas las obligaciones de seguridad de una operación unsafe; programadores y revisores deben justificarlas.
Esto sigue siendo una gran ventaja práctica frente a C o C++: Rust hace más visibles las operaciones sensibles a memoria y sus obligaciones, para que las revisiones se centren en los límites unsafe y en las abstracciones safe construidas sobre ellos. Incluyen bloques unsafe, contratos de funciones unsafe, implementaciones de traits unsafe, declaraciones FFI y atributos unsafe; no siempre se limitan a un bloque pequeño. Un caso concreto es CVE-2021-25900 en smallvec: SmallVec::insert_many reservaba usando el límite inferior del tamaño del iterador y escribía más elementos de los que cabían en la asignación; recibió CVSS 9.8.
Soundness es el estándar, no la prudencia
Una API es no sound si código safe puede usarla para provocar comportamiento indefinido. Por ejemplo, Iterator::size_hint ofrece límites para optimizar, no un recuento exacto: un iterador válido puede producir más elementos que el límite inferior, y uno defectuoso puede incumplir su estimación. La documentación estándar indica expresamente que esta sugerencia no debe usarse para eliminar comprobaciones de límites en código unsafe. Un trait safe con comportamiento incorrecto también puede causar errores lógicos o panics; eso no es automáticamente UB, pero el código unsafe debe conservar las invariantes de memoria incluso ante código del llamador inesperado.
El código unsafe no está aislado localmente: una API safe puede depender de una invariante de memoria establecida en otro punto. Si es incorrecta, el defecto está en la abstracción que expuso la API safe, no en los llamadores que la usan conforme al contrato.
Reglas prácticas
- ✓No trate métodos de traits safe como pruebas de seguridad de memoria. Use
size_hint()solo para optimizar; mantenga las comprobaciones o amplíe capacidad cuando haga falta. - ✓Conserve las invariantes ante panics. El código del llamador, como
Iterator::next, un destructor o una closure, puede desenrollar la pila. Asegúrese de que el estado parcialmente inicializado siga siendo válido y se destruya correctamente. - ✓Escriba un comentario
// SAFETY:que indique qué condiciones deben cumplirse y por qué. El código unsafe sin explicación no se puede revisar. - ✓Mantenga los bloques acotados. Envolver un bucle entero para no repetir la palabra clave amplía lo que debe analizar el revisor.
- ✓Ejecute Miri sobre pruebas concretas. Puede detectar algunos casos de UB en rutas ejecutadas, como accesos fuera de límites, use-after-free y aliasing inválido; no demuestra que todo el código unsafe sea sound.
- ✓Audite las dependencias.
cargo auditcomprueba avisos conocidos de RustSec;cargo geigerayuda a inventariar el uso de unsafe. Ninguna herramienta sustituye revisar los contratos unsafe del código publicado.
Preguntas frecuentes
#![forbid(unsafe_code)] puede impedir la sintaxis unsafe en el crate donde se activa, algo útil en crates de aplicación. No afecta a las dependencias, y algunas implementaciones, interfaces FFI y bibliotecas necesitan operaciones unsafe.
Sí. Un unsafe trait documenta obligaciones que deben cumplir sus implementaciones; el código unsafe puede confiar en ellas. TrustedLen es un trait inestable de la biblioteca estándar para iteradores que prometen una sugerencia de tamaño exacta. El Iterator::size_hint normal no ofrece esa garantía.
No. El modelo de Rust reduce dónde aparecen las obligaciones de seguridad de memoria y las hace explícitas en los límites unsafe. Aun así, hay que auditar código unsafe, dependencias, contratos FFI y APIs safe construidas sobre implementaciones unsafe.
Referencias
- The Rust Reference — unsafe code and soundness
- Rust Reference — the unsafe keyword and proof obligations
- Rust standard library — Iterator::size_hint contract
- Rustonomicon — working with unsafe
- RustSec Advisory RUSTSEC-2021-0003 — smallvec buffer overflow
- GitHub Security Advisory GHSA-43w2-9j62-hq99 — CVE-2021-25900
- The Rustonomicon — the guide to unsafe Rust
- RustSec Advisory Database
- Miri — an interpreter that detects undefined behaviour
- CWE-787 — Out-of-bounds Write
- Case study: the smallvec overflow (CVE-2021-25900)