FlawOpen

flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021

● servo/rust-smallvec · CVE-2021-25900 · January 2021 · CVSS 9.8 · Crítica
Investigación · FlawOpen

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.

💡 Explicación en Lenguaje Sencillo (ELI5)

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

  • Referencia CWE para la debilidad descrita en esta guía: CWE-122 CWE-787

Cronología del Incidente

2021-01-08

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.

2021-01

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

Paso 1

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.

Paso 2

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.

Paso 3

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.

Paso 4

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

IMPLEMENTACIÓN VULNERABLE
// 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);
    }
}
PARCHE SEGURO Y ROBUSTO
// 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

Fuentes