Ruzzy
Ruzzy é um fuzzer orientado por cobertura para Ruby construído sobre o libFuzzer. Ele permite fuzzing tanto de código Ruby puro quanto de extensões C do Ruby, com suporte a sanitizadores para detecção de corrupção de memória e comportamento indefinido.
Quando Usar
Atualmente, o Ruzzy é o único fuzzer orientado por cobertura pronto para produção para Ruby.
Escolha o Ruzzy quando:
- Fizer fuzzing de aplicações ou bibliotecas Ruby
- Testar extensões C do Ruby quanto a problemas de segurança de memória
- Precisar de fuzzing orientado por cobertura para código Ruby
- Trabalhar com gems Ruby que possuem extensões nativas
Início Rápido
Configure o ambiente:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Teste com o exemplo de brinquedo incluso:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
Isso deve encontrar rapidamente uma falha, demonstrando que o Ruzzy está funcionando corretamente.
Instalação
Suporte a Plataformas
O Ruzzy suporta Linux x86-64 e AArch64/ARM64. Para macOS ou Windows, use o Dockerfile ou o ambiente de desenvolvimento.
Pré-requisitos
- Linux x86-64 ou AArch64/ARM64
- Versão recente do clang (testada a partir da 14.0.0, recomenda-se a versão mais recente)
- Ruby com gem instalada
Comando de Instalação
Instale o Ruzzy com as flags do compilador clang:
MAKE="make --environment-overrides V=1" \
CC="/caminho/para/clang" \
CXX="/caminho/para/clang++" \
LDSHARED="/caminho/para/clang -shared" \
LDSHAREDXX="/caminho/para/clang++ -shared" \
gem install ruzzy
Variáveis de ambiente explicadas:
MAKE: Sobrescreve o make para respeitar as variáveis de ambiente subsequentesCC,CXX,LDSHARED,LDSHAREDXX: Garantem o uso dos binários clang corretos para recursos mais recentes
Solução de Problemas na Instalação
Se a instalação falhar, ative a saída de depuração:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Verificação
Verifique a instalação executando o exemplo de brinquedo (veja a seção Início Rápido).
Escrevendo um Harness
Fuzzing de Código Ruby Puro
O fuzzing de Ruby puro requer dois scripts devido a detalhes de implementação do interpretador Ruby.
Script tracer (test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Script harness (test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# Seu código a ser fuzzeado aqui
if input.length == 4
if input[0] == 'F'
if input[1] == 'U'
if input[2] == 'Z'
if input[3] == 'Z'
raise
end
end
end
end
end
end
test_one_input = lambda do |data|
fuzzing_target(data)
return 0
end
Ruzzy.fuzz(test_one_input)
Execute com:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Fuzzing de Extensões C do Ruby
Extensões C podem ser fuzzeadas com um único arquivo harness, sem necessidade de tracer.
Exemplo de harness para msgpack (fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# Estamos procurando por corrupção de memória, não exceções Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Execute com:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Regras do Harness
| Faça | Não Faça |
|---|---|
| Capture exceções Ruby se estiver testando extensões C | Deixe exceções Ruby quebrarem o fuzzer |
| Retorne 0 do lambda test_one_input | Retorne outros valores |
| Mantenha o harness determinístico | Use aleatoriedade ou lógica baseada em tempo |
| Use script tracer para Ruby puro | Pule o tracer para código Ruby puro |
Veja Também: Para técnicas detalhadas de escrita de harness, padrões para lidar com entradas complexas e estratégias avançadas, consulte a habilidade técnica fuzz-harness-writing.
Compilação
Instalando Gems com Sanitizadores
Ao instalar gems Ruby com extensões C para fuzzing, compile com flags de sanitizador:
MAKE="make --environment-overrides V=1" \
CC="/caminho/para/clang" \
CXX="/caminho/para/clang++" \
LDSHARED="/caminho/para/clang -shared" \
LDSHAREDXX="/caminho/para/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install <nome-da-gem>
Flags de Compilação
| Flag | Propósito |
|---|---|
-fsanitize=address,fuzzer-no-link | Ativa o AddressSanitizer e a instrumentação do fuzzer |
-fno-omit-frame-pointer | Melhora a qualidade do stack trace |
-fno-common | Melhor compatibilidade com sanitizadores |
-fPIC | Código independente de posição para bibliotecas compartilhadas |
-g | Inclui símbolos de depuração |
Executando Campanhas
Configuração do Ambiente
Antes de executar qualquer campanha de fuzzing, configure ASAN_OPTIONS:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Opções explicadas:
allocator_may_return_null=1: Ignora falhas comuns de alocação de baixo impacto (DoS)detect_leaks=0: O interpretador Ruby vaza dados; ignore isso por enquantouse_sigaltstack=0: O Ruby recomenda desabilitar sigaltstack com ASan
Execução Básica
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Nota: LD_PRELOAD é necessário para a injeção do sanitizador. Ao contrário de ASAN_OPTIONS, não o exporte, pois pode interferir com outros programas.
Com Corpus
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /caminho/para/corpus
Passando Opções do libFuzzer
Todas as opções do libFuzzer podem ser passadas como argumentos:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /caminho/para/corpus -max_len=1024 -timeout=10
Consulte opções do libFuzzer para referência completa.
Reproduzindo Falhas
Reexecute um caso de falha passando o arquivo de falha:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
Interpretando a Saída
| Saída | Significado |
|---|---|
INFO: Running with entropic power schedule | Campanha de fuzzing iniciada |
ERROR: AddressSanitizer: heap-use-after-free | Corrupção de memória detectada |
SUMMARY: libFuzzer: fuzz target exited | Exceção Ruby ocorreu |
artifact_prefix='./'; Test unit written to ./crash-* | Entrada de falha salva |
Base64: ... | Codificação Base64 da entrada de falha |
Integração com Sanitizadores
AddressSanitizer (ASan)
O Ruzzy inclui uma biblioteca AddressSanitizer pré-compilada:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Use ASan para detectar:
- Estouro de buffer no heap
- Estouro de buffer na pilha
- Use-after-free
- Double-free
- Vazamentos de memória (desabilitados por padrão no Ruzzy)
UndefinedBehaviorSanitizer (UBSan)
O Ruzzy também inclui UBSan:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
Use UBSan para detectar:
- Estouro de inteiro com sinal
- Desreferências de ponteiro nulo
- Acesso a memória desalinhada
- Divisão por zero
Problemas Comuns com Sanitizadores
| Problema | Solução |
|---|---|
| Avisos de vazamento do interpretador Ruby | Use ASAN_OPTIONS=detect_leaks=0 |
| Conflitos de Sigaltstack | Use ASAN_OPTIONS=use_sigaltstack=0 |
| Spam de falha de alocação | Use ASAN_OPTIONS=allocator_may_return_null=1 |
| LD_PRELOAD interfere com ferramentas | Não o exporte; defina inline com o comando ruby |
Veja Também: Para configuração detalhada de sanitizadores, problemas comuns e flags avançadas, consulte as habilidades técnicas address-sanitizer e undefined-behavior-sanitizer.
Exemplos do Mundo Real
Exemplo: msgpack-ruby
Fuzzing do analisador MessagePack msgpack para corrupção de memória.
Instale com sanitizadores:
MAKE="make --environment-overrides V=1" \
CC="/caminho/para/clang" \
CXX="/caminho/para/clang++" \
LDSHARED="/caminho/para/clang -shared" \
LDSHAREDXX="/caminho/para/clang++ -shared" \
CFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
CXXFLAGS="-fsanitize=address,fuzzer-no-link -fno-omit-frame-pointer -fno-common -fPIC -g" \
gem install msgpack
Harness (fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# Estamos procurando por corrupção de memória, não exceções Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Execute:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Exemplo: Alvo Ruby Puro
Fuzzing de código Ruby puro com um analisador personalizado.
Tracer (test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Harness (test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
require_relative 'my_parser'
test_one_input = lambda do |data|
begin
MyParser.parse(data)
rescue StandardError
# Exceções esperadas de entrada malformada
end
return 0
end
Ruzzy.fuzz(test_one_input)
Execute:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Solução de Problemas
| Problema | Causa | Solução |
|---|---|---|
| Instalação falha | Versão ou caminho do clang errado | Verifique o caminho do clang, use clang 14.0.0+ |
cannot open shared object file | LD_PRELOAD não definido | Defina LD_PRELOAD inline com o comando ruby |
| Fuzzer sai imediatamente | Diretório de corpus ausente | Crie o diretório de corpus ou passe-o como argumento |
| Sem progresso de cobertura | Ruby puro precisa de tracer | Use script tracer para código Ruby puro |
| Spam de detecção de vazamento | O interpretador Ruby vaza | Defina ASAN_OPTIONS=detect_leaks=0 |
| Depuração de instalação necessária | Erros de compilação | Use RUZZY_DEBUG=1 gem install --verbose ruzzy |
Habilidades Relacionadas
Habilidades Técnicas
| Habilidade | Caso de Uso |
|---|---|
| fuzz-harness-writing | Orientação detalhada sobre como escrever harnesses eficazes |
| address-sanitizer | Detecção de erros de memória durante o fuzzing |
| undefined-behavior-sanitizer | Detecção de comportamento indefinido em extensões C |
| libfuzzer | Entendendo as opções do libFuzzer (o Ruzzy é construído sobre o libFuzzer) |
Fuzzers Relacionados
| Habilidade | Quando Considerar |
|---|---|
| libfuzzer | Ao fazer fuzzing de código de extensão C do Ruby diretamente em C/C++ |
| aflpp | Abordagem alternativa para fuzzing Ruby instrumentando o interpretador Ruby |
Recursos
Principais Recursos Externos
Apresentando o Ruzzy, um fuzzer Ruby orientado por cobertura Postagem oficial do blog da Trail of Bits anunciando o Ruzzy, cobrindo motivação, arquitetura e resultados iniciais.
Repositório GitHub do Ruzzy Código-fonte, exemplos adicionais e instruções de desenvolvimento.
Documentação do libFuzzer Como o Ruzzy é construído sobre o libFuzzer, entender as opções e o comportamento do libFuzzer é valioso.
Fuzzing de extensões C do Ruby Guia detalhado sobre fuzzing de extensões C com flags de compilação e exemplos.
Fuzzing de código Ruby puro Guia detalhado sobre o padrão de tracer necessário para fuzzing de Ruby puro.