ruzzy について
ruzzyはlibFuzzerを基盤とする、Ruby向けのカバレッジガイド型ファザーです。純粋なRubyコードとRuby C拡張をテストでき、メモリ破損や未定義動作の検出にサニタイザーを利用します。
主な機能
- 01Ruby向けカバレッジガイド型ファジング
- 02純粋なRubyとC拡張のハーネス
- 03AddressSanitizerとUBSanのサポート
- 04libFuzzerオプションとコーパス入力
活用シーン
- 01Rubyアプリやライブラリのファジング
- 02Ruby C拡張のメモリ安全性の確認
- 03保存済み入力によるクラッシュの再現
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill ruzzyインストール後、Claude Code で/ruzzyを入力して読み込まれたか確認します。
同梱ファイルagents/openai.yamlassets/trail-of-bits-mark.svg
詳細
Ruzzy
Ruzzy は libFuzzer を基盤とする、Ruby 向けのカバレッジ誘導型ファザーです。純粋な Ruby コードと Ruby C 拡張の両方をファジングでき、サニタイザーを使ってメモリ破損や未定義動作を検出できます。
使用する場面
Ruzzy は現在、Ruby 向けの唯一の本番運用可能なカバレッジ誘導型ファザーです。
次の場合は Ruzzy を選択してください:
- Ruby アプリケーションやライブラリをファジングする場合
- Ruby C 拡張のメモリ安全性の問題をテストする場合
- Ruby コードに対するカバレッジ誘導型ファジングが必要な場合
- ネイティブ拡張を含む Ruby gem を扱う場合
クイックスタート
環境をセットアップします:
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 -e 'require "ruzzy"; Ruzzy.dummy'
これにより、Ruzzy が正常に動作していることを示すクラッシュがすぐに見つかります。
インストール
プラットフォームのサポート
Ruzzy は Linux x86-64 および AArch64/ARM64 をサポートしています。macOS または Windows では、Dockerfile または開発環境を使用してください。
前提条件
- Linux x86-64 または AArch64/ARM64
- 最近のバージョンの clang(14.0.0 までのバージョンでテスト済み。最新リリースを推奨)
- gem がインストールされた Ruby
インストールコマンド
clang コンパイラーフラグを指定して Ruzzy をインストールします:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/clang++ -shared" \
gem install ruzzy
環境変数の説明:
MAKE:後続の環境変数が反映されるように make を上書きしますCC、CXX、LDSHARED、LDSHAREDXX:最新機能を利用できるよう、適切な clang バイナリが使われるようにします
インストール時のトラブルシューティング
インストールに失敗する場合は、デバッグ出力を有効にします:
RUZZY_DEBUG=1 gem install --verbose ruzzy
検証
トイサンプルを実行してインストールを検証します(「クイックスタート」セクションを参照)。
ハーネスの作成
純粋な Ruby コードのファジング
Ruby インタープリターの実装上の詳細により、純粋な Ruby のファジングには 2 つのスクリプトが必要です。
トレーサースクリプト(test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
ハーネススクリプト(test_harness.rb):
# frozen_string_literal: true
require 'ruzzy'
def fuzzing_target(input)
# Your code to fuzz here
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)
次のように実行します:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby test_tracer.rb
Ruby C 拡張のファジング
C 拡張は、トレーサーを使わず、単一のハーネスファイルでファジングできます。
msgpack 用ハーネスの例(fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
次のように実行します:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby fuzz_msgpack.rb
ハーネスのルール
| すること | しないこと |
|---|---|
| C 拡張をテストする場合は Ruby の例外を捕捉する | Ruby の例外でファザーをクラッシュさせる |
test_one_input ラムダから 0 を返す | ほかの値を返す |
| ハーネスを決定論的に保つ | 乱数や時刻に依存するロジックを使う |
| 純粋な Ruby にはトレーサースクリプトを使う | 純粋な Ruby コードでトレーサーを省略する |
関連項目: ハーネスの詳細な作成手法、複雑な入力を処理するパターン、 高度な戦略については、fuzz-harness-writing 技術スキルを参照してください。
コンパイル
サニタイザーを使った gem のインストール
ファジング用に C 拡張を含む Ruby gem をインストールする場合は、サニタイザーフラグを指定してコンパイルします:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/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 <gem-name>
ビルドフラグ
| フラグ | 目的 |
|---|---|
-fsanitize=address,fuzzer-no-link | AddressSanitizer とファザーの計測を有効にする |
-fno-omit-frame-pointer | スタックトレースの品質を向上させる |
-fno-common | サニタイザーとの互換性を高める |
-fPIC | 共有ライブラリ向けの位置独立コードを生成する |
-g | デバッグシンボルを含める |
ファジングキャンペーンの実行
環境のセットアップ
ファジングキャンペーンを実行する前に、ASAN_OPTIONS を設定します:
export ASAN_OPTIONS="allocator_may_return_null=1:detect_leaks=0:use_sigaltstack=0"
オプションの説明:
allocator_may_return_null=1:影響の小さい一般的な割り当て失敗(DoS)を無視しますdetect_leaks=0:Ruby インタープリターはデータをリークするため、現時点では無視しますuse_sigaltstack=0:Ruby は ASan での sigaltstack の無効化を推奨しています
基本的な実行
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
注: サニタイザーを注入するには LD_PRELOAD が必要です。ASAN_OPTIONS とは異なり、ほかのプログラムに干渉する可能性があるため、エクスポートしないでください。
コーパスを指定した実行
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus
libFuzzer オプションの指定
すべての libFuzzer オプションを引数として渡せます:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb /path/to/corpus -max_len=1024 -timeout=10
詳細については、libFuzzer のオプションを参照してください。
クラッシュの再現
クラッシュファイルを渡して、クラッシュしたケースを再実行します:
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb ./crash-253420c1158bc6382093d409ce2e9cff5806e980
出力の解釈
| 出力 | 意味 |
|---|---|
INFO: Running with entropic power schedule | ファジングキャンペーンが開始されました |
ERROR: AddressSanitizer: heap-use-after-free | メモリ破損が検出されました |
SUMMARY: libFuzzer: fuzz target exited | Ruby の例外が発生しました |
artifact_prefix='./'; Test unit written to ./crash-* | クラッシュ入力が保存されました |
Base64: ... | クラッシュ入力の Base64 エンコード |
サニタイザーとの統合
AddressSanitizer(ASan)
Ruzzy には、コンパイル済みの AddressSanitizer ライブラリが含まれています。
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::ASAN_PATH') \
ruby harness.rb
次の検出には ASan を使用します。
- ヒープバッファーオーバーフロー
- スタックバッファーオーバーフロー
- 解放後使用
- 二重解放
- メモリリーク(Ruzzy ではデフォルトで無効)
UndefinedBehaviorSanitizer(UBSan)
Ruzzy には UBSan も含まれています。
LD_PRELOAD=$(ruby -e 'require "ruzzy"; print Ruzzy::UBSAN_PATH') \
ruby harness.rb
次の検出には UBSan を使用します。
- 符号付き整数のオーバーフロー
- Null ポインターの参照外し
- アラインメントされていないメモリアクセス
- ゼロ除算
よくあるサニタイザーの問題
| 問題 | 解決策 |
|---|---|
| Ruby インタープリターのリーク警告 | ASAN_OPTIONS=detect_leaks=0 を使用します |
| Sigaltstack の競合 | ASAN_OPTIONS=use_sigaltstack=0 を使用します |
| アロケーション失敗の警告が大量に出る | ASAN_OPTIONS=allocator_may_return_null=1 を使用します |
| LD_PRELOAD がツールに干渉する | エクスポートせず、ruby コマンド内で指定します |
関連項目: 詳細なサニタイザーの設定、よくある問題、上級者向けのフラグについては、 address-sanitizer と undefined-behavior-sanitizer のテクニックスキルを参照してください。
実例
例:msgpack-ruby
メモリ破損を検出するために、msgpack MessagePack パーサーをファジングします。
サニタイザーを使用してインストール:
MAKE="make --environment-overrides V=1" \
CC="/path/to/clang" \
CXX="/path/to/clang++" \
LDSHARED="/path/to/clang -shared" \
LDSHAREDXX="/path/to/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
ハーネス(fuzz_msgpack.rb):
# frozen_string_literal: true
require 'msgpack'
require 'ruzzy'
test_one_input = lambda do |data|
begin
MessagePack.unpack(data)
rescue Exception
# We're looking for memory corruption, not Ruby exceptions
end
return 0
end
Ruzzy.fuzz(test_one_input)
実行:
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
例:Pure Ruby ターゲット
カスタムパーサーを使って Pure Ruby コードをファジングします。
トレーサー(test_tracer.rb):
# frozen_string_literal: true
require 'ruzzy'
Ruzzy.trace('test_harness.rb')
ハーネス(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
# Expected exceptions from malformed input
end
return 0
end
Ruzzy.fuzz(test_one_input)
実行:
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
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| インストールに失敗する | clang のバージョンまたはパスが正しくない | clang のパスを確認し、clang 14.0.0 以降を使用します |
cannot open shared object file | LD_PRELOAD が設定されていない | ruby コマンド内で LD_PRELOAD を指定します |
| ファザーがすぐに終了する | コーパスディレクトリがない | コーパスディレクトリを作成するか、引数として渡します |
| カバレッジの進捗がない | Pure Ruby ではトレーサーが必要 | Pure Ruby コードにはトレーサースクリプトを使用します |
| リーク検出の警告が大量に出る | Ruby インタープリターのリーク | ASAN_OPTIONS=detect_leaks=0 を設定します |
| インストールのデバッグが必要 | コンパイルエラー | RUZZY_DEBUG=1 gem install --verbose ruzzy を使用します |
関連スキル
テクニックスキル
| スキル | 使用例 |
|---|---|
| fuzz-harness-writing | 効果的なハーネスの作成に関する詳しいガイダンス |
| address-sanitizer | ファジング中のメモリエラー検出 |
| undefined-behavior-sanitizer | C 拡張機能における未定義動作の検出 |
| libfuzzer | libFuzzer のオプションを理解する(Ruzzy は libFuzzer を基盤としています) |
関連ファザー
| スキル | 検討するタイミング |
|---|---|
| libfuzzer | Ruby の C 拡張機能を C/C++ で直接ファジングする場合 |
| aflpp | Ruby インタープリターに計測を組み込んで Ruby をファジングする代替手法 |
リソース
主な外部リソース
Ruzzy の紹介:カバレッジガイド型 Ruby ファザー Ruzzy を発表する Trail of Bits 公式ブログ記事です。開発の動機、アーキテクチャ、初期の成果について説明しています。
Ruzzy GitHub リポジトリ ソースコード、追加のサンプル、開発手順を確認できます。
libFuzzer のドキュメント Ruzzy は libFuzzer をベースに構築されているため、libFuzzer のオプションや動作を理解しておくと役立ちます。
Ruby の C 拡張をファジングする コンパイルフラグやサンプルを含む、C 拡張のファジングに関する詳細なガイドです。
純粋な Ruby コードをファジングする 純粋な Ruby コードのファジングに必要なトレーサーパターンに関する詳細なガイドです。