FlawOpen

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

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

CVE-2021-25900: overflow no smallvec do Rust

SmallVec::insert_many confiava no limite inferior de size_hint; se o iterador rendesse mais, ocorria overflow do heap. Corrigido em 0.6.14 e 1.6.1.

💡 Explicação em Linguagem Simples (ELI5)

Um armazém é famoso por ser seguro porque um robô confere cada prateleira antes de algo ser colocado nela. Em algumas tarefas o robô é desligado e uma pessoa trabalha à mão, prometendo ter cuidado. Uma dessas pessoas perguntou ao motorista "mais ou menos quantas caixas você traz?", montou uma prateleira desse tamanho e começou a empilhar. A estimativa era um palpite, não uma garantia, e quando chegaram mais caixas do que o prometido, a pilha passou do fim da prateleira.

Conceitos Centrais e Termos

SmallVec
Uma coleção do Rust que guarda poucos elementos inline e passa para um buffer no heap quando cresce. Sua rapidez vem de código unsafe que gerencia esse buffer manualmente.
size_hint
Um método de Iterator que informa limites estimados, inferior e superior, de quantos itens faltam. É uma dica: código safe pode devolver qualquer valor, até um errado.
Rust unsafe
Código dentro de um bloco unsafe pode fazer coisas que o compilador não verifica, como gravar por ponteiros brutos; o autor precisa garantir a segurança de memória para qualquer chamador safe.
Soundness
Uma API safe é sound se nenhum código safe, por mais estranho que seja, consegue causar comportamento indefinido por meio dela. insert_many não era, porque um size_hint válido podia corromper a memória.

Mapeamentos de frameworks

Linha do Tempo do Incidente

2021-01-08

Yechan Bae e o grupo de Rust do SSLab da Georgia Tech relatam o bug; a RustSec publica o RUSTSEC-2021-0003 e o smallvec 0.6.14 e 1.6.1 trazem a correção.

2021-01

A CVE-2021-25900 é atribuída; o cargo audit passa a apontar as versões afetadas nas árvores de dependências.

Fluxo de Ataque Passo a Paso

Passo 1

Um iterador informa um tamanho menor

insert_many é chamado com um iterador cujo limite inferior do size_hint é menor que o número de itens que ele realmente produz. Código safe tem permissão para isso.

Passo 2

O espaço é reservado pela dica

insert_many reserva espaço só para o limite inferior e desloca os elementos existentes nessa quantidade.

Passo 3

Todos os itens são gravados mesmo assim

Dentro de um bloco unsafe, o laço grava cada item por um ponteiro bruto, e os itens extras caem depois do fim da alocação.

Passo 4

O heap é corrompido

O overflow sobrescreve memória vizinha no heap. Programas que passam a insert_many iteradores influenciados por entrada não confiável ficam expostos.

Código-Fonte: Vulnerável vs. Seguro

IMPLEMENTAÇÃO VULNERÁVEL
// Aloque espaço com base em uma *estimativa*...
let (lower, _) = iter.size_hint();
self.reserve(lower);

unsafe {
    let ptr = self.as_mut_ptr().add(index);
    for (i, item) in iter.enumerate() {
        // ...mas grave a quantidade de
        // itens que realmente chegar.
        ptr::write(ptr.add(i), item);
    }
}
PATCH SEGURO E ROBUSTO
// Verifique novamente a capacidade para cada item.
// Nunca permita que um valor de código seguro
// determine um limite 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);
    }
}

// A correção real também reduziu bastante
// a superfície de código unsafe.

Lista de Verificação de Segurança para Engenharia

Fontes