flawopen.com/Segurança de memória/Rust unsafe
O bloco unsafe do Rust continua seguro para a memória?
unsafe não desativa as verificações do Rust. Ele permite operações específicas cujas condições de segurança o compilador não consegue provar; o programador precisa cumprir essas obrigações. O ponto essencial é que uma abstração unsafe não sound pode permitir que chamadas safe comuns causem comportamento indefinido (UB), mesmo sem usar a palavra unsafe.
Termos-chave
- Operação unsafe:
- Operação com condições de segurança que o compilador não consegue provar, como desreferenciar um ponteiro raw.
- Abstração sound:
- API safe cujos chamadores safe não conseguem causar comportamento indefinido.
- size_hint():
- Estimativa do tamanho de um iterador para otimização; não é uma contagem exata nem prova de segurança de memória.
- Comportamento indefinido (UB):
- Comportamento que o Rust não define; código unsafe deve impedir que chamadores safe o provoquem.
Como uma abstração safe não sound falha
Uma API safe recebe um iterador
O chamador fornece um iterador comum a uma função sem pré-condição unsafe.
A implementação reserva com base em uma estimativa
A função reserva apenas o limite inferior informado, que pode ser menor que o número de itens produzidos.
Escritas sem verificação excedem a capacidade
A implementação grava cada item por um ponteiro raw sem aumentar a alocação nem conferir a capacidade a cada item.
O uso safe alcança UB
Se o iterador produzir mais itens do que cabem, uma chamada safe pode escrever fora dos limites. Use Vec::extend ou outra implementação que aumente a capacidade com segurança.
// 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); }
O que a garantia do Rust realmente diz
A garantia precisa é: Rust safe não pode causar comportamento indefinido, desde que o código unsafe do qual depende seja sound. A Rust Reference define soundness na fronteira: código unsafe deve garantir que clientes safe não provoquem UB. O compilador não verifica todas as obrigações de segurança dentro de uma operação unsafe; programadores e revisores precisam comprová-las.
Isso ainda é uma grande vantagem prática em relação a C e C++: Rust deixa operações sensíveis à memória e suas obrigações mais visíveis, permitindo que revisores se concentrem nas fronteiras unsafe e nas abstrações safe construídas sobre elas. Essas fronteiras incluem blocos unsafe, contratos de funções unsafe, implementações de traits unsafe, declarações FFI e atributos unsafe; não ficam sempre restritas a um único bloco pequeno. Um caso concreto é o CVE-2021-25900 no smallvec: SmallVec::insert_many reservava espaço usando o limite inferior de um iterador e gravava mais elementos do que a alocação comportava; a gravidade foi CVSS 9.8.
Soundness é o padrão, não cuidado
Uma API é não sound se código safe puder usá-la para causar comportamento indefinido. Por exemplo, Iterator::size_hint fornece limites úteis para otimização, não uma contagem exata: um iterador válido pode produzir mais itens que o limite inferior, e um iterador com defeito pode violar sua estimativa. A documentação padrão diz explicitamente que essa dica não deve ser usada para remover verificações de limites em código unsafe. Comportamentos incorretos de traits safe também podem causar erros lógicos ou panics; isso não é automaticamente UB, mas o código unsafe deve preservar invariantes de memória mesmo quando o código fornecido pelo chamador se comporta de modo inesperado.
O código unsafe não é local: uma invariante de memória estabelecida em um ponto pode ser usada por APIs safe acima dele. Se a invariante estiver errada, o defeito está na abstração que expôs a API safe, não nos chamadores safe que a usam conforme o contrato.
Regras práticas
- ✓Não trate métodos de traits safe como prova de segurança de memória. Use
size_hint()apenas para otimizar; preserve verificações ou aumente a capacidade quando necessário. - ✓Preserve invariantes durante panics. Código fornecido pelo chamador, como
Iterator::next, um destrutor ou uma closure, pode desenrolar a pilha. Garanta que o estado parcialmente inicializado continue válido e seja descartado corretamente. - ✓Escreva um comentário
// SAFETY:explicando o que precisa ser verdadeiro e por quê. Código unsafe sem justificativa não pode ser revisado. - ✓Mantenha blocos estreitos. Envolver um loop inteiro para evitar repetir a palavra-chave aumenta o que o revisor precisa analisar.
- ✓Execute Miri nos testes exercitados. Ele pode detectar alguns casos de UB nos caminhos executados, como acesso fora dos limites, use-after-free e aliasing inválido; não prova que todo código unsafe é sound.
- ✓Audite dependências.
cargo auditverifica avisos RustSec conhecidos;cargo geigerajuda a inventariar o uso de unsafe. Nenhuma ferramenta substitui a revisão dos contratos unsafe do código enviado.
Perguntas frequentes
#![forbid(unsafe_code)] pode impedir a sintaxe unsafe no crate onde é habilitada, o que é útil para crates de aplicação. Não controla dependências, e algumas implementações, fronteiras FFI e bibliotecas precisam de operações unsafe.
Sim. Uma unsafe trait documenta obrigações que as implementações precisam cumprir; código unsafe pode confiar nelas. TrustedLen é uma trait instável da biblioteca padrão para iteradores que prometem uma estimativa de tamanho exata. Iterator::size_hint comum não oferece essa garantia.
Não. O modelo de segurança do Rust reduz onde surgem obrigações de segurança de memória e as torna explícitas nas fronteiras unsafe. Ainda é necessário auditar código unsafe, dependências, contratos FFI e APIs safe construídas sobre implementações unsafe.
Referências
- 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)