flawopen.com/Incidents/rust-unsafe-smallvec-overflow-january-2021
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.
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
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.
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
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.
O espaço é reservado pela dica
insert_many reserva espaço só para o limite inferior e desloca os elementos existentes nessa quantidade.
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.
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
// 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);
}
}// 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
- ✓Nunca dimensione um buffer usado por código unsafe a partir de um método de trait safe, como size_hint, len num trait object ou um comparador; confira a capacidade antes de cada escrita.
- ✓Escreva um comentário // SAFETY: em cada bloco unsafe dizendo em que invariante ele se apoia, e rejeite revisões em que o invariante dependa de código safe não confiável.
- ✓Rode o Miri no CI com testes que usem implementações hostis, como um iterador cujo size_hint mente, um Drop que entra em pânico e um comparador que não é uma ordem total.
- ✓Mantenha os blocos unsafe o menor possível; a correção do smallvec também reduziu o código unsafe de insert_many para que ele pudesse ser verificado.
- ✓Rode cargo audit (ou cargo deny) no CI para que dependências com avisos da RustSec, como smallvec anterior a 0.6.14 ou 1.6.1, reprovem o build.
Fontes
- RustSec — RUSTSEC-2021-0003: buffer overflow em SmallVec::insert_many
- servo/rust-smallvec — issue 252
- CVE-2021-25900 — registro CVE
- MITRE CWE-787
- Heartbleed: uma verificação de limite ausente, metade da internet
- Baron Samedit: dez anos de root escondidos numa barra invertida
- Citrix Bleed: como ler errado um valor de retorno anulou o MFA