O que é o Kani
O Kani e uma ferramenta de verificação formal para a linguagem Rust. Em vez de rodar testes com entradas específicas, o Kani usa uma técnica chamada model checking para explorar matematicamente todos os possíveis estados do programa e provar que determinadas propriedades sempre se mantém, ou encontrar um contraexemplo que demonstre quando a propriedade falha.
A ferramenta foi desenvolvida pela AWS (Amazon Web Services) e e open source. O objetivo e complementar os testes tradicionais e o fuzzing, oferecendo um nível mais alto de garantia para código crítico em segurança, como drivers, sistemas embarcados, código criptográfico e infraestrutura de nuvem.
Rust já tem uma proposta de segurança forte com seu sistema de ownership e borrow checker. O Kani vai um passo além e verifica propriedades em tempo de execução que o compilador não consegue checar estaticamente, como ausência de overflow, panic, acessos fora de limites e condições de corrida em código unsafe.
Kani e especialmente útil para verificar código unsafe em Rust. O compilador confia em você nesse contexto - o Kani não.
Como funciona
O Kani funciona traduzindo o código Rust para um formato intermediário chamado GOTO-programs, que é então analisado por um verificador de modelos chamado CBMC (C Bounded Model Checker). O CBMC explora simbolicamente todos os caminhos de execução possíveis dentro de limites configurados.
Você escreve harnesses de verificação, que são funções anotadas com #[kani::proof]. Dentro do harness, você define entradas simbólicas usando kani::any(), que representa qualquer valor possível do tipo. O Kani então verifica se as propriedades que você declarou se mantem para todas essas entradas.
Quando o Kani encontra uma violação, ele retorna um counterexample: os valores específicos de entrada que causam o problema. Isso e diferente de um fuzzer que também busca crashes, porque o Kani e exaustivo dentro dos limites definidos. Se ele diz que não encontrou problema, e porque verificou todos os casos possíveis.
Model checking e computacionalmente caro. Para funções com muitos estados possíveis, o Kani pode levar muito tempo ou atingir limites de memoria. Use em funções críticas e bem delimitadas.
Principais recursos
- Verificação de ausência de panic: prova que seu código nunca vai dar unwrap em None, índex out of bounds, overflow, etc.
- Verificação de segurança de memoria: encontra uso de memoria não inicializada e outros problemas em blocos unsafe.
- Verificação de invariantes customizados: você define as propriedades que quer provar com macros como kani::assert e kani::assume.
- Integração com Cargo: roda com cargo kani, sem necessidade de toolchain separado.
- Contraexemplos concretos: quando falha, retorna valores específicos que reproduzem o problema.
- Compatibilidade com rustc: usa o mesmo compilador Rust, sem fork ou versão especial da linguagem.
A ferramenta também suporta verificação de código concorrente, embora com limitações, e funciona com a maioria das crates da biblioteca padrão.
Como começar: instalação passo a passo
O Kani requer o Rust toolchain instalado. Se você ainda não tem, instale via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shInstale o Kani via cargo:
cargo install --locked kani-verifier\ncargo kani setupO setup vai baixar os componentes necessários, incluindo o CBMC. Depois, adicione um harness de verificação no seu código:
#[cfg(kani)]\n#[kani::proof]\nfn verificar_soma() {\n let a: u8 = kani::any();\n let b: u8 = kani::any();\n kani::assume(a < 100 && b < 100);\n let resultado = a + b;\n assert!(resultado < 200);\n}Execute a verificação:
cargo kaniExemplo prático: verificando um parser
Imagine uma função que faz parse de um cabeçalho de protocolo binário:
fn parse_header(data: &[u8]) -> Option {\n if data.len() < 4 {\n return None;\n }\n Some(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))\n} Com Kani, você verifica que nunca vai ocorrer panic independente da entrada:
#[cfg(kani)]\n#[kani::proof]\nfn verificar_parse_header() {\n const MAX_LEN: usize = 8;\n let len: usize = kani::any();\n kani::assume(len <= MAX_LEN);\n let data: [u8; MAX_LEN] = kani::any();\n let slice = &data[..len];\n let _ = parse_header(slice); // Kani verifica: nunca da panic\n}Se houvesse um bug onde o código acessasse data[4] sem checar o tamanho, o Kani encontraria o contraexemplo com len=4 e mostraria exatamente qual entrada causa o problema.
Comparação com alternativas
O Kani ocupa um espaço diferente das outras ferramentas de qualidade de código:
- Testes unitários: verificam casos específicos que você escolhe. Rápidos, mas podem deixar passar casos extremos que você não pensou.
- Fuzzing (cargo-fuzz, libFuzzer): gera entradas aleatórias buscando crashes. Muito eficaz para encontrar bugs, mas não e exaustivo - não prova ausência de problemas.
- Miri: interpreta o código Rust em modo estrito para detectar comportamento indefinido. Não e formal mas captura muitos bugs de unsafe. Mais rápido que Kani.
- Verificadores de tipos (Prusti, Creusot): usam anotações de especificação no código. Mais expressivos que Kani mas exigem mais esforço do desenvolvedor.
A posição do Kani e no sweet spot: mais rigoroso que fuzzing, mas mais automático que verificadores baseados em especificação.
Use Kani junto com fuzzing, não em vez dele. Fuzzing e eficaz para encontrar bugs complexos em caminhos profundos. Kani e melhor para verificar invariantes formais em funções delimitadas.
Pontos positivos e limitações
Pontos positivos: prova matemática de ausência de certos bugs, integração simples com Cargo, contraexemplos concretos quando encontra problemas, open source e mantido pela AWS, ideal para código crítico.
Limitações: verificação e cara computacionalmente, pode ser impraticável para funções com muitos estados. Requer que você escreva harnesses adequados, o que tem curva de aprendizado. Não substitui testes para lógica de negócio complexa. Lida mal com entrada de dados externa grande (arquivos, rede) sem estratégia de absstração.
Casos de uso reais
- Drivers e firmware: código que roda em nível baixo sem ambiente de teste fácil. A AWS usa Kani para verificar partes do Firecracker, o VMM open source.
- Bibliotecas criptográficas: qualquer código de criptografia onde um edge case pode ser catastrófico. Verificar que padding, mascaramento e operações em bits sempre se comportam como esperado.
- Parsers de protocolo: parsers são notoriamente difíceis de testar exaustivamente. Kani verifica que nenhuma entrada causa panic ou comportamento indefinido.
- Código embedded: microcontroladores onde não existe memoria virtual e qualquer acesso fora de limites e desastroso.
Dicas e boas práticas
Comece com harnesses pequenos e bem delimitados. Verificar uma função que faz parse de 4 bytes e fácil. Verificar um sistema inteiro de IO e impraticável. Escale gradualmente.
Use kani::assume() com cuidado. Restrições demais podem fazer o Kani verificar apenas um subconjunto dos casos interessantes, dando falsa confiança.
Adicione cargo kani no seu CI para as funções críticas. A integração e simples e o tempo de verificação e previsível uma vez que os harnesses estejam bem definidos.
Kani não substitui code review para lógica de negócio. Se o algoritmo estiver errado conceitualmente, o Kani não vai detectar isso, ele apenas garante que o código faz o que você especificou nos harnesses.
Vale a pena?
Para quem escreve código Rust em áreas críticas como segurança, sistemas embarcados, drivers ou infraestrutura, o Kani e uma adição valiosa ao toolkit de qualidade. A curva de aprendizado e razoável e a garantia que a ferramenta oferece e genuinamente superior ao que testes convencionais entregam.
Para desenvolvimento de aplicações Rust comuns, como web APIs ou CLIs, o esforço provavelmente não se justifica. Testes unitários e de integração resolvem bem esses casos com muito menos overhead.
O próximo passo e instalar via cargo kani setup e ler os exemplos do repositório oficial no GitHub da AWS. O repositório tem uma coleção de harnesses de exemplo que mostram padoes práticos para os casos de uso mais comuns.
Comentários
Deixar um comentárioVocê precisa ter uma conta no CuritibaBlog para comentar.