flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
CVE-2021-25900: overflow de heap en smallvec
SmallVec::insert_many confiaba en el límite inferior de size_hint; si el iterador producía más, desbordaba el búfer. Corregido en 0.6.14 y 1.6.1.
Un almacén es famoso por su seguridad porque un robot revisa cada estantería antes de colocar nada en ella. Para algunas tareas se apaga el robot y trabaja una persona a mano, que promete tener cuidado. Una de esas personas preguntó al conductor «¿cuántas cajas traes, más o menos?», montó una estantería de ese tamaño y empezó a apilar. La estimación era una suposición, no una garantía, y cuando llegaron más cajas de las prometidas, la pila se salió de la estantería.
Conceptos Clave y Términos
SmallVec- Una colección de Rust que guarda pocos elementos en línea y pasa a un búfer en el heap cuando crece. Su rapidez viene de código unsafe que gestiona ese búfer a mano.
size_hint- Un método de Iterator que informa de unos límites estimados, inferior y superior, de cuántos elementos quedan. Es una pista: el código safe puede devolver cualquier valor, incluso uno incorrecto.
Rust unsafe- El código dentro de un bloque unsafe puede hacer cosas que el compilador no comprueba, como escribir mediante punteros crudos; el autor debe garantizar la seguridad de memoria para cualquier llamante safe.
Solidez (soundness)- Una API safe es sólida si ningún código safe, por raro que sea, puede provocar comportamiento indefinido a través de ella. insert_many no lo era, porque un size_hint válido podía corromper la memoria.
Mapeos de marcos
Cronología del Incidente
Yechan Bae y el grupo de Rust del SSLab de Georgia Tech notifican el fallo; RustSec publica RUSTSEC-2021-0003 y smallvec 0.6.14 y 1.6.1 incluyen la corrección.
Se asigna CVE-2021-25900; cargo audit empieza a señalar las versiones afectadas en los árboles de dependencias.
Flujo de Ataque Paso a Paso
Un iterador declara menos de lo que tiene
Se llama a insert_many con un iterador cuyo límite inferior de size_hint es menor que el número de elementos que realmente produce. El código safe puede hacerlo.
Se reserva espacio según la pista
insert_many reserva sitio solo para el límite inferior y desplaza los elementos existentes en esa cantidad.
Se escriben todos los elementos igualmente
Dentro de un bloque unsafe, el bucle escribe cada elemento mediante un puntero crudo, de modo que los sobrantes caen más allá del final de la reserva.
Se corrompe el heap
El desbordamiento sobrescribe memoria contigua del heap. Quedan expuestos los programas que pasan a insert_many iteradores influidos por entradas no confiables.
Código Fuente: Vulnerable vs. Seguro
// Asigne espacio según una *estimación*...
let (lower, _) = iter.size_hint();
self.reserve(lower);
unsafe {
let ptr = self.as_mut_ptr().add(index);
for (i, item) in iter.enumerate() {
// ...pero escriba tantos
// elementos como lleguen realmente.
ptr::write(ptr.add(i), item);
}
}// Vuelva a comprobar la capacidad para cada elemento.
// No permita que un valor procedente de código seguro
// determine un límite usado por código unsafe.
for (i, item) in iter.enumerate() {
self.reserve(1);
unsafe {
let ptr = self.as_mut_ptr()
.add(index + i);
ptr::write(ptr, item);
}
}
// La corrección real también redujo considerablemente
// la superficie de código unsafe.Lista de Verificación de Seguridad para Ingeniería
- ✓Nunca dimensiones un búfer usado por código unsafe a partir de un método de trait safe, como size_hint, len en un trait object o un comparador; vuelve a comprobar la capacidad antes de cada escritura.
- ✓Escribe un comentario // SAFETY: en cada bloque unsafe con el invariante del que depende, y rechaza revisiones en las que ese invariante dependa de código safe no confiable.
- ✓Ejecuta Miri en CI con pruebas que usen implementaciones hostiles, como un iterador cuyo size_hint miente, un Drop que entra en pánico y un comparador que no es un orden total.
- ✓Mantén los bloques unsafe lo más pequeños posible; la corrección de smallvec también redujo el código unsafe de insert_many para poder verificarlo.
- ✓Ejecuta cargo audit (o cargo deny) en CI para que las dependencias con avisos de RustSec, como smallvec anterior a 0.6.14 o 1.6.1, hagan fallar la compilación.
Fuentes
- RustSec — RUSTSEC-2021-0003: desbordamiento de búfer en SmallVec::insert_many
- servo/rust-smallvec — issue 252
- CVE-2021-25900 — registro CVE
- MITRE CWE-787
- Heartbleed: una comprobación de límites que faltaba, medio internet
- Baron Samedit: diez años de root escondidos en una barra invertida
- Citrix Bleed: cómo leer mal un valor de retorno anuló el MFA