Ruzzy
Ruzzy es un fuzzer guiado por cobertura para Ruby construido sobre libFuzzer. Permite fuzzing tanto de código Ruby puro como de extensiones C de Ruby con soporte de sanitizadores para detectar corrupción de memoria y comportamiento indefinido.
Cuándo usar
Ruzzy es actualmente el único fuzzer guiado por cobertura listo para producción para Ruby.
Elige Ruzzy cuando:
- Realices fuzzing de aplicaciones o bibliotecas Ruby
- Pruebes extensiones C de Ruby para problemas de seguridad de memoria
- Necesites fuzzing guiado por cobertura para código Ruby
- Trabajes con gemas Ruby que tengan extensiones nativas
Inicio rápido
Configura el entorno:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Prueba con el ejemplo incluido:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
Esto debería encontrar rápidamente un fallo que demuestre que Ruzzy funciona correctamente.
Instalación
Soporte de plataforma
Ruzzy soporta Linux x86-64 y AArch64/ARM64. Para macOS o Windows, usa el Dockerfile o el entorno de desarrollo.
Prerrequisitos
- Linux x86-64 o AArch64/ARM64
- Versión reciente de clang (probado desde 14.0.0, se recomienda la última versión)
- Ruby con gem instalado
Comando de instalación
Instala Ruzzy con las banderas del compilador clang:
MAKE="make --environment-overrides V=1" \
CC="/ruta/a/clang" \
CXX="/ruta/a/clang++" \
LDSHARED="/ruta/a/clang -shared" \
LDSHAREDXX="/ruta/a/clang++ -shared" \
gem install ruzzy
Variables de entorno explicadas:
MAKE: Sobrescribe make para respetar las variables de entorno subsiguientesCC,CXX,LDSHARED,LDSHAREDXX: Aseguran que se usen los binarios clang adecuados para las últimas características
Solución de problemas de instalación
Si la instalación falla, habilita la salida de depuración:
RUZZY_DEBUG=1 gem install --verbose ruzzy
Verificación
Verifica la instalación ejecutando el ejemplo de juguete (ver sección de Inicio rápido).
Escribiendo un harness
Fuzzing de código Ruby puro
El fuzzing de Ruby puro requiere dos scripts debido a detalles de implementación del intérprete 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)
# Tu código a fuzzear aquí
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)
Ejecuta con:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Fuzzing de extensiones C de Ruby
Las extensiones C se pueden fuzzear con un solo archivo harness, sin necesidad de tracer.
Ejemplo 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
# Buscamos corrupción de memoria, no excepciones Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Ejecuta con:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Reglas del harness
| Haz | No hagas |
|---|---|
| Captura excepciones Ruby si pruebas extensiones C | Dejes que excepciones Ruby fallen el fuzzer |
| Devuelve 0 de la lambda test_one_input | Devuelvas otros valores |
| Mantén el harness determinista | Uses aleatoriedad o lógica basada en tiempo |
| Usa el script tracer para Ruby puro | Omitas el tracer para código Ruby puro |
Ver también: Para técnicas detalladas de escritura de harnesses, patrones para manejar entradas complejas y estrategias avanzadas, consulta la habilidad técnica fuzz-harness-writing.
Compilación
Instalando gemas con sanitizadores
Al instalar gemas Ruby con extensiones C para fuzzing, compila con banderas de sanitización:
MAKE="make --environment-overrides V=1" \
CC="/ruta/a/clang" \
CXX="/ruta/a/clang++" \
LDSHARED="/ruta/a/clang -shared" \
LDSHAREDXX="/ruta/a/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 <nombre-de-la-gema>
Banderas de compilación
| Bandera | Propósito |
|---|---|
-fsanitize=address,fuzzer-no-link | Habilita AddressSanitizer e instrumentación del fuzzer |
-fno-omit-frame-pointer | Mejora la calidad de los stack traces |
-fno-common | Mejor compatibilidad con sanitizadores |
-fPIC | Código independiente de posición para bibliotecas compartidas |
-g | Incluye símbolos de depuración |
Ejecutando campañas
Configuración del entorno
Antes de ejecutar cualquier campaña de fuzzing, configura ASAN_OPTIONS:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Opciones explicadas:
allocator_may_return_null=1: Omite fallos de asignación comunes de bajo impacto (DoS)detect_leaks=0: El intérprete Ruby filtra datos, ignora esto por ahorause_sigaltstack=0: Ruby recomienda deshabilitar sigaltstack con ASan
Ejecución básica
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Nota: LD_PRELOAD es necesario para la inyección del sanitizador. A diferencia de ASAN_OPTIONS, no lo exportes ya que podría interferir con otros programas.
Con corpus
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /ruta/al/corpus
Pasando opciones de libFuzzer
Todas las opciones de libFuzzer se pueden pasar como argumentos:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /ruta/al/corpus -max_len=1024 -timeout=10
Consulta opciones de libFuzzer para la referencia completa.
Reproduciendo fallos
Vuelve a ejecutar un caso de fallo pasando el archivo:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./fallo-253420c1158bc6382093d409ce2e9cff5806e980
Interpretando la salida
| Salida | Significado |
|---|---|
INFO: Running with entropic power schedule | Campaña de fuzzing iniciada |
ERROR: AddressSanitizer: heap-use-after-free | Corrupción de memoria detectada |
SUMMARY: libFuzzer: fuzz target exited | Ocurrió una excepción Ruby |
artifact_prefix='./'; Test unit written to ./fallo-* | Entrada de fallo guardada |
Base64: ... | Codificación Base64 de la entrada de fallo |
Integración de sanitizadores
AddressSanitizer (ASan)
Ruzzy incluye una biblioteca AddressSanitizer precompilada:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Usa ASan para detectar:
- Desbordamientos de búfer en heap
- Desbordamientos de búfer en pila
- Use-after-free
- Double-free
- Fugas de memoria (deshabilitadas por defecto en Ruzzy)
UndefinedBehaviorSanitizer (UBSan)
Ruzzy también incluye UBSan:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
Usa UBSan para detectar:
- Desbordamiento de enteros con signo
- Desreferencias de puntero nulo
- Acceso a memoria desalineada
- División por cero
Problemas comunes con sanitizadores
| Problema | Solución |
|---|---|
| Advertencias de fuga del intérprete Ruby | Usa ASAN_OPTIONS=detect_leaks=0 |
| Conflictos de sigaltstack | Usa ASAN_OPTIONS=use_sigaltstack=0 |
| Spam de fallos de asignación | Usa ASAN_OPTIONS=allocator_may_return_null=1 |
| LD_PRELOAD interfiere con herramientas | No lo exportes; configúralo en línea con el comando ruby |
Ver también: Para configuración detallada de sanitizadores, problemas comunes y banderas avanzadas, consulta las habilidades técnicas address-sanitizer y undefined-behavior-sanitizer.
Ejemplos del mundo real
Ejemplo: msgpack-ruby
Fuzzing del analizador MessagePack msgpack para corrupción de memoria.
Instalación con sanitizadores:
MAKE="make --environment-overrides V=1" \
CC="/ruta/a/clang" \
CXX="/ruta/a/clang++" \
LDSHARED="/ruta/a/clang -shared" \
LDSHAREDXX="/ruta/a/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
# Buscamos corrupción de memoria, no excepciones Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Ejecución:
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
Ejemplo: Objetivo Ruby puro
Fuzzing de código Ruby puro con un analizador 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
# Excepciones esperadas de entrada malformada
end
return 0
end
Ruzzy.fuzz(test_one_input)
Ejecución:
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
Solución de problemas
| Problema | Causa | Solución |
|---|---|---|
| La instalación falla | Versión o ruta de clang incorrecta | Verifica la ruta de clang, usa clang 14.0.0+ |
cannot open shared object file | LD_PRELOAD no configurado | Configura LD_PRELOAD en línea con el comando ruby |
| El fuzzer sale inmediatamente | Falta el directorio del corpus | Crea el directorio del corpus o pásalo como argumento |
| Sin progreso de cobertura | Ruby puro necesita tracer | Usa el script tracer para código Ruby puro |
| Spam de detección de fugas | Fugas del intérprete Ruby | Configura ASAN_OPTIONS=detect_leaks=0 |
| Depuración de instalación necesaria | Errores de compilación | Usa RUZZY_DEBUG=1 gem install --verbose ruzzy |
Habilidades relacionadas
Habilidades técnicas
| Habilidad | Caso de uso |
|---|---|
| fuzz-harness-writing | Guía detallada para escribir harnesses efectivos |
| address-sanitizer | Detección de errores de memoria durante el fuzzing |
| undefined-behavior-sanitizer | Detección de comportamiento indefinido en extensiones C |
| libfuzzer | Comprensión de opciones de libFuzzer (Ruzzy está construido sobre libFuzzer) |
Fuzzers relacionados
| Habilidad | Cuándo considerar |
|---|---|
| libfuzzer | Al fuzzear código de extensión C de Ruby directamente en C/C++ |
| aflpp | Enfoque alternativo para fuzzear Ruby instrumentando el intérprete Ruby |
Recursos
Recursos externos clave
Presentando Ruzzy, un fuzzer Ruby guiado por cobertura Publicación oficial del blog de Trail of Bits anunciando Ruzzy, cubriendo motivación, arquitectura y resultados iniciales.
Repositorio GitHub de Ruzzy Código fuente, ejemplos adicionales e instrucciones de desarrollo.
Documentación de libFuzzer Dado que Ruzzy está construido sobre libFuzzer, comprender las opciones y el comportamiento de libFuzzer es valioso.
Fuzzing de extensiones C de Ruby Guía detallada sobre fuzzing de extensiones C con banderas de compilación y ejemplos.
Fuzzing de código Ruby puro Guía detallada sobre el patrón de tracer requerido para fuzzing de Ruby puro.