コンテンツにスキップ

LLVM/libFuzzer

出典: フリー教科書『ウィキブックス(Wikibooks)』


libFuzzer は、LLVM の SanitizerCoverage 計装を使用してコードカバレッジを最大化するために、入力データの変異を生成するインプロセスのカバレッジガイド型進化的ファズィングエンジンです。このエンジンは、テスト対象のライブラリとリンクすることで動作します。

状況

[編集]
現在の開発
元の著者は別のファズィングエンジンである Centipede に注力しているため、大きな新機能の追加は期待されていませんが、重要なバグ修正は引き続き行われます。

バージョン

[編集]
  • libFuzzer は、対応するバージョンの Clang を必要とします。

始めに

[編集]

ファズターゲット

[編集]
定義
テスト対象の API を使用してバイト配列を処理する関数を実装します。
:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  DoSomethingInterestingWithMyAPI(Data, Size);
  return 0;
}
要件
どんな入力にも対応し、非決定論を避け、迅速に実行し、グローバル状態を変更しないようにします。

ファザーの使用方法

[編集]
コンパイル
clang -g -O1 -fsanitize=fuzzer,address mytarget.c
オプション
ASAN, UBSAN, MSAN などのサニタイザーと組み合わせて使用します。

コーパス

[編集]
定義
サンプル入力のコレクションです。
管理
-merge=1 フラグを使用してコーパスを最小化または拡張します。

実行

[編集]
セットアップ
初期入力をディレクトリに配置し、ファザーを実行します。
mkdir CORPUS_DIR
cp /some/input/samples/* CORPUS_DIR
./my_fuzzer CORPUS_DIR

並列ファズィング

[編集]
複数のプロセス
-jobs=N-workers=N を使用して複数のファズィングジョブを並列で実行します。

並列ファズテスト

[編集]

並列実行の設定とオプション

[編集]

libFuzzerは効率的な並列ファズテストをサポートしています。主な設定オプションは:

  • -jobs=N:並列に実行するジョブの数
  • -workers=N:各ジョブで使用するワーカープロセスの数
  • -fork=N:フォークモードでの子プロセス数

並列実行時は共有コーパスディレクトリを使用し、発見された新しい入力は自動的に共有されます。

フォークモード

[編集]

フォークモードの使用方法と利点

[編集]

フォークモードは、メインプロセスが定期的に子プロセスを生成してファズテストを実行する方式です。利点:

  • メモリリークの検出が容易
  • クラッシュ後も自動的に再起動
  • リソース使用量の制御が可能
使用例:
./fuzzer -fork=8 corpus_dir/  # 8個の子プロセスを使用

マージの再開

[編集]

マージプロセスの途中再開方法

[編集]

コーパスマージが中断された場合、以下の手順で再開できます:

  1. -merge_control_file オプションを使用してマージ状態を保存
  2. 中断時のファイルを指定して再開
    ./fuzzer -merge=1 -merge_control_file=merge.txt corpus_dir1/ corpus_dir2/
    

オプション

[編集]

主要なコマンドラインオプションの詳細

[編集]
  • -max_len=N:入力の最大長を設定
  • -dict=file:カスタム辞書ファイルを指定
  • -timeout=N:タイムアウト時間(秒)を設定
  • -artifact_prefix=prefix:生成されるファイルのプレフィックスを設定

その他のオプションとその使用例

[編集]
  • -print_final_stats=1:終了時に詳細な統計を表示
  • -max_total_time=N:総実行時間の制限を設定
  • -shrink=1:コーパスの縮小を実行

出力

[編集]

ファズテスト実行中の出力情報の解説

[編集]

libFuzzerは実行中、以下の情報を出力します:

  • 実行回数と速度
  • 発見されたパスの数
  • カバレッジ情報
  • クラッシュやタイムアウトの詳細

各イベントコードと統計情報の説明

[編集]

主な統計情報:

  • #0 start_time:開始時刻
  • #1 new_units_added:新しく追加された入力の数
  • #2 new_features:発見された新機能の数
  • #3 peak_rss:最大メモリ使用量

使用例

[編集]

トイ例

[編集]

シンプルなファズターゲットの例

[編集]

以下は文字列処理のシンプルな例です:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  std::string str(reinterpret_cast<const char*>(Data), Size);
  if (str.find("MAGIC") != std::string::npos &&
      str.find("MAGIC2") != std::string::npos &&
      str.find("MAGIC3") != std::string::npos)
    abort();  // 人工的なバグ
  return 0;
}

実行方法と期待される結果

[編集]
clang++ -fsanitize=fuzzer,address toy_example.cpp -o toy_fuzzer
./toy_fuzzer

このファザーは "MAGIC"、"MAGIC2"、"MAGIC3" を含む入力を見つけようとします。

実践例

[編集]

実際のファズターゲットと発見されたバグの例

[編集]
OpenSSLのASN.1パーサーのファズテスト例:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
  const unsigned char *p = Data;
  X509 *x509 = d2i_X509(NULL, &p, Size);
  if (x509) X509_free(x509);
  return 0;
}

詳細な解説と分析

[編集]
  • メモリの解放が適切に行われていることを確認
  • 入力サイズの制限を設定
  • ASN.1構造の異常値をテスト

高度な機能

[編集]

辞書の使用

[編集]

ユーザー提供の辞書によるファズテストの強化

[編集]
辞書ファイルの形式:
# コメント
"key1"  # ASCII文字列
"key2\x42"  # エスケープシーケンス
使用方法:
./fuzzer -dict=./dict.txt corpus/

CMP命令のトレース

[編集]

CMP命令のトレースとその効果

[編集]
  • -use_traces=1 オプションで有効化
  • 比較命令の両オペランドを記録
  • 効率的な入力生成に活用

バリュープロファイル

[編集]

バリュープロファイルの使用方法と利点

[編集]
  • -use_value_profile=1 で有効化
  • 比較命令の結果を追跡
  • 新しいコードパスの発見を促進

ファズフレンドリービルドモード

[編集]

ファズテストに適したビルド設定の推奨

[編集]
推奨設定:
  • -O1 最適化レベル
  • デバッグ情報の保持 (-g)
  • サニタイザーの有効化
  • 決定論的ビルド

AFL互換性

[編集]

AFLとの連携方法とベストプラクティス

[編集]
  • AFL形式のコーパスの使用
  • AFLの統計形式のサポート
  • クロスファジング技術

Fuzzerの評価

[編集]

ファズターゲットとコーパスの評価方法

[編集]
評価基準:
  • カバレッジの測定
  • 実行速度の計測
  • メモリ使用量の監視
  • バグ発見能力の評価

ユーザー提供のミューテーター

[編集]

カスタムミューテーターの使用方法

[編集]
カスタムミューテーターの実装例:
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
                                         size_t MaxSize, unsigned int Seed) {
  // カスタムな変異ロジック
  return NewSize;
}

開発者向け情報

[編集]

libFuzzerの開発方法と貢献方法

[編集]
  1. ソースコードの取得:
  • git clone https://github.com/llvm/llvm-project.git
    cd llvm-project/compiler-rt/lib/fuzzer
    
  1. テストの実行:
  • cmake -DCOMPILER_RT_INCLUDE_TESTS=ON
    ninja check-fuzzer
    

コードレビューとバグ報告のガイドライン

[編集]
  • バグ報告には最小の再現ケースを含める
  • パッチはLLVMのコーディング規約に従う
  • 単体テストを追加
  • レビュー前にローカルテストを実行

FAQ

[編集]

よくある質問と回答

[編集]
Q
メモリ使用量が増加し続ける場合は?
A
-rss_limit_mb=N オプションでメモリ制限を設定
Q
特定のパスを見つけられない場合は?
A
カスタム辞書の使用や -use_value_profile=1 を試す
Q
クラッシュの再現方法は?
A
crash-* ファイルを使用して再実行

トロフィー

[編集]

libFuzzerが発見した著名なバグとその詳細

[編集]
  • OpenSSL: 複数のASN.1パース関連のバグ
  • SQLite: NULL pointer dereference
  • Chromium: 多数のメモリ破壊バグ
  • libxml2: バッファオーバーフロー
  • LLVM: JIT コンパイラのバグ

これらのバグの多くはセキュリティ上重要な問題として修正されました。