Ruzzy
Ruzzy est un fuzzer guidé par couverture pour Ruby construit sur libFuzzer. Il permet de fuzzer à la fois du code Ruby pur et des extensions C Ruby avec le support des sanitizers pour détecter les corruptions mémoire et les comportements indéfinis.
Quand l'utiliser
Ruzzy est actuellement le seul fuzzer guidé par couverture prêt pour la production pour Ruby.
Choisissez Ruzzy quand :
- Vous fuzzez des applications ou bibliothèques Ruby
- Vous testez des extensions C Ruby pour des problèmes de sécurité mémoire
- Vous avez besoin de fuzzing guidé par couverture pour du code Ruby
- Vous travaillez avec des gems Ruby qui ont des extensions natives
Démarrage rapide
Configurez l'environnement :
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Testez avec l'exemple jouet inclus :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby -e 'require "ruzzy"; Ruzzy.dummy'
Cela devrait trouver rapidement un crash démontrant que Ruzzy fonctionne correctement.
Installation
Support de plateforme
Ruzzy prend en charge Linux x86-64 et AArch64/ARM64. Pour macOS ou Windows, utilisez le Dockerfile ou l'environnement de développement.
Prérequis
- Linux x86-64 ou AArch64/ARM64
- Version récente de clang (testé à partir de 14.0.0, dernière version recommandée)
- Ruby avec gem installé
Commande d'installation
Installez Ruzzy avec les drapeaux de compilation clang :
MAKE="make --environment-overrides V=1" \
CC="/chemin/vers/clang" \
CXX="/chemin/vers/clang++" \
LDSHARED="/chemin/vers/clang -shared" \
LDSHAREDXX="/chemin/vers/clang++ -shared" \
gem install ruzzy
Explication des variables d'environnement :
MAKE: Remplace make pour respecter les variables d'environnement suivantesCC,CXX,LDSHARED,LDSHAREDXX: Assure l'utilisation des bons binaires clang pour les fonctionnalités les plus récentes
Dépannage de l'installation
Si l'installation échoue, activez le débogage :
RUZZY_DEBUG=1 gem install --verbose ruzzy
Vérification
Vérifiez l'installation en exécutant l'exemple jouet (voir la section Démarrage rapide).
Écrire un harnais
Fuzzing de code Ruby pur
Le fuzzing de Ruby pur nécessite deux scripts en raison des détails d'implémentation de l'interpréteur Ruby.
Script traceur (test_tracer.rb) :
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Script harnais (test_harness.rb) :
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# Votre code à fuzzer ici
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)
Exécutez avec :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Fuzzing d'extensions C Ruby
Les extensions C peuvent être fuzzées avec un seul fichier harnais, sans traceur.
Exemple de harnais pour msgpack (fuzz_msgpack.rb) :
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# Nous cherchons des corruptions mémoire, pas des exceptions Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Exécutez avec :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
Règles pour les harnais
| À faire | À ne pas faire |
|---|---|
| Attraper les exceptions Ruby si vous testez des extensions C | Laisser les exceptions Ruby planter le fuzzer |
| Retourner 0 depuis la lambda test_one_input | Retourner d'autres valeurs |
| Garder le harnais déterministe | Utiliser de l'aléatoire ou une logique basée sur le temps |
| Utiliser un script traceur pour le Ruby pur | Sauter le traceur pour du code Ruby pur |
Voir aussi : Pour des techniques détaillées d'écriture de harnais, des motifs pour gérer des entrées complexes, et des stratégies avancées, consultez la compétence technique fuzz-harness-writing.
Compilation
Installer des gems avec les sanitizers
Lors de l'installation de gems Ruby avec des extensions C pour le fuzzing, compilez avec les drapeaux de sanitizer :
MAKE="make --environment-overrides V=1" \
CC="/chemin/vers/clang" \
CXX="/chemin/vers/clang++" \
LDSHARED="/chemin/vers/clang -shared" \
LDSHAREDXX="/chemin/vers/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 <nom-de-la-gem>
Drapeaux de compilation
| Drapeau | Objectif |
|---|---|
-fsanitize=address,fuzzer-no-link | Activer AddressSanitizer et l'instrumentation fuzzer |
-fno-omit-frame-pointer | Améliorer la qualité des traces de pile |
-fno-common | Meilleure compatibilité avec les sanitizers |
-fPIC | Code indépendant de la position pour les bibliothèques partagées |
-g | Inclure les symboles de débogage |
Exécuter des campagnes
Configuration de l'environnement
Avant d'exécuter une campagne de fuzzing, définissez ASAN_OPTIONS :
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
Explication des options :
allocator_may_return_null=1: Ignorer les échecs d'allocation courants à faible impact (DoS)detect_leaks=0: L'interpréteur Ruby fuit des données, ignorer ces fuites pour l'instantuse_sigaltstack=0: Ruby recommande de désactiver sigaltstack avec ASan
Exécution de base
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Remarque : LD_PRELOAD est requis pour l'injection du sanitizer. Contrairement à ASAN_OPTIONS, ne l'exportez pas car cela pourrait interférer avec d'autres programmes.
Avec un corpus
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /chemin/vers/corpus
Passer des options à libFuzzer
Toutes les options de libFuzzer peuvent être passées comme arguments :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /chemin/vers/corpus -max_len=1024 -timeout=10
Consultez les options de libFuzzer pour une référence complète.
Reproduire des crashes
Ré-exécutez un cas de crash en passant le fichier de crash :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
Interpréter la sortie
| Sortie | Signification |
|---|---|
INFO: Running with entropic power schedule | Campagne de fuzzing démarrée |
ERROR: AddressSanitizer: heap-use-after-free | Corruption mémoire détectée |
SUMMARY: libFuzzer: fuzz target exited | Exception Ruby survenue |
artifact_prefix='./'; Test unit written to ./crash-* | Entrée de crash sauvegardée |
Base64: ... | Encodage Base64 de l'entrée de crash |
Intégration des sanitizers
AddressSanitizer (ASan)
Ruzzy inclut une bibliothèque AddressSanitizer pré-compilée :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
Utilisez ASan pour détecter :
- Les dépassements de tampon sur le tas
- Les dépassements de tampon sur la pile
- Les use-after-free
- Les double-free
- Les fuites mémoire (désactivées par défaut dans Ruzzy)
UndefinedBehaviorSanitizer (UBSan)
Ruzzy inclut également UBSan :
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
Utilisez UBSan pour détecter :
- Les débordements d'entiers signés
- Les déréférencements de pointeur nul
- Les accès mémoire mal alignés
- Les divisions par zéro
Problèmes courants avec les sanitizers
| Problème | Solution |
|---|---|
| Avertissements de fuite de l'interpréteur Ruby | Utiliser ASAN_OPTIONS=detect_leaks=0 |
| Conflits Sigaltstack | Utiliser ASAN_OPTIONS=use_sigaltstack=0 |
| Spam d'échecs d'allocation | Utiliser ASAN_OPTIONS=allocator_may_return_null=1 |
| LD_PRELOAD interfère avec les outils | Ne pas l'exporter ; le définir en ligne avec la commande ruby |
Voir aussi : Pour une configuration détaillée des sanitizers, des problèmes courants et des drapeaux avancés, consultez les compétences techniques address-sanitizer et undefined-behavior-sanitizer.
Exemples concrets
Exemple : msgpack-ruby
Fuzzing de l'analyseur MessagePack msgpack pour les corruptions mémoire.
Installation avec sanitizers :
MAKE="make --environment-overrides V=1" \
CC="/chemin/vers/clang" \
CXX="/chemin/vers/clang++" \
LDSHARED="/chemin/vers/clang -shared" \
LDSHAREDXX="/chemin/vers/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
Harnais (fuzz_msgpack.rb) :
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# Nous cherchons des corruptions mémoire, pas des exceptions Ruby
end
return 0
end
Ruzzy.fuzz(test_one_input)
Exécution :
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
Exemple : Cible Ruby pur
Fuzzing de code Ruby pur avec un analyseur personnalisé.
Traceur (test_tracer.rb) :
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
Harnais (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
# Exceptions attendues d'une entrée mal formée
end
return 0
end
Ruzzy.fuzz(test_one_input)
Exécution :
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
Dépannage
| Problème | Cause | Solution |
|---|---|---|
| L'installation échoue | Mauvaise version ou chemin de clang | Vérifier le chemin de clang, utiliser clang 14.0.0+ |
cannot open shared object file | LD_PRELOAD non défini | Définir LD_PRELOAD en ligne avec la commande ruby |
| Le fuzzer se termine immédiatement | Dossier corpus manquant | Créer le dossier corpus ou le passer comme argument |
| Pas de progression de couverture | Le Ruby pur nécessite un traceur | Utiliser un script traceur pour le code Ruby pur |
| Spam de détection de fuite | Fuites de l'interpréteur Ruby | Définir ASAN_OPTIONS=detect_leaks=0 |
| Débogage d'installation nécessaire | Erreurs de compilation | Utiliser RUZZY_DEBUG=1 gem install --verbose ruzzy |
Compétences associées
Compétences techniques
| Compétence | Cas d'utilisation |
|---|---|
| fuzz-harness-writing | Guide détaillé pour écrire des harnais efficaces |
| address-sanitizer | Détection d'erreurs mémoire pendant le fuzzing |
| undefined-behavior-sanitizer | Détection de comportements indéfinis dans les extensions C |
| libfuzzer | Comprendre les options de libFuzzer (Ruzzy est basé sur libFuzzer) |
Fuzzers associés
| Compétence | Quand l'envisager |
|---|---|
| libfuzzer | Lors du fuzzing direct de code d'extension C Ruby en C/C++ |
| aflpp | Approche alternative pour fuzzer Ruby en instrumentant l'interpréteur Ruby |
Ressources
Ressources externes clés
Présentation de Ruzzy, un fuzzer Ruby guidé par couverture Article officiel du blog de Trail of Bits annonçant Ruzzy, couvrant la motivation, l'architecture et les premiers résultats.
Dépôt GitHub de Ruzzy Code source, exemples supplémentaires et instructions de développement.
Documentation de libFuzzer Puisque Ruzzy est basé sur libFuzzer, comprendre les options et le comportement de libFuzzer est précieux.
Fuzzing des extensions C Ruby Guide détaillé sur le fuzzing des extensions C avec des drapeaux de compilation et des exemples.
Fuzzing de code Ruby pur Guide détaillé sur le motif de traceur requis pour le fuzzing de Ruby pur.