mcpField
F
fuzzing-obstacles

fuzzing-obstacles

github.com/trailofbitsテスト・品質7.9k5.4k 回インストール今日更新
リポジトリ

fuzzing-obstacles について

この技法ガイドでは、本番時の動作を保ちながら、条件付きコンパイルでファジング用に被テストコードを調整する方法を説明します。探索を妨げる検証や非決定的な状態への対処と、誤検出リスクの評価を扱います。

主な機能

  1. 01検証やグローバル状態の障害を特定
  2. 02条件付きコンパイルでファジング用ビルドを調整
  3. 03固定シードで動作を決定的にする
  4. 04カバレッジ変化を段階的に測定
  5. 05チェックの迂回による誤検出リスクを評価

活用シーン

  1. 01チェックサムが深いコードパスへの到達を阻む場合
  2. 02時刻ベースのシードで実行が不安定な場合
  3. 03複雑な形式検証で有効入力の生成が困難な場合
  4. 04カバレッジで広範囲の未到達コードが見つかった場合

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trailofbits/skills --skill fuzzing-obstacles

インストール後、Claude Code で/fuzzing-obstaclesを入力して読み込まれたか確認します。

同梱ファイルagents/openai.yamlassets/trail-of-bits-mark.svg

詳細

ファジングの障害を克服する

コードベースには、十分なカバレッジを妨げるファジング対策パターンが含まれていることがよくあります。チェックサム、グローバル状態(時刻をシードとする PRNG など)、検証チェックによって、ファザーがより深いコードパスを探索できなくなる場合があります。この手法では、製品版の動作を維持しながら、ファジング中にこれらの障害を回避できるよう、System Under Test(SUT)にパッチを適用する方法を紹介します。

概要

実際のプログラムの多くは、ファジングを考慮して設計されていません。次のような場合があります。

  • 入力を処理する前にチェックサムや暗号学的ハッシュを検証する
  • グローバル状態(システム時刻、環境変数など)に依存する
  • 非決定的な乱数生成器を使用する
  • ファザーが有効な入力を生成しにくくなる複雑な検証を行う

これらのパターンがあると、次の理由からファジングが困難になります。

  1. チェックサム: ファザーは正しいハッシュ値を推測する必要があります(その可能性は天文学的に低いです)
  2. グローバル状態: 同じ入力でも実行ごとに動作が異なります(決定性が損なわれます)
  3. 複雑な検証: ファザーが、より深いコードを探索する代わりに検証エラーへの到達に労力を費やします

解決策は条件付きコンパイルです。ファジング用ビルドではコードの動作を変更し、製品版のコードは変更せずに保ちます。

主な概念
概念説明
SUT のパッチ適用ファジングに適したものにするため、System Under Test を変更すること
条件付きコンパイルコンパイル時フラグに応じて動作が異なるコード
ファジング用ビルドモードファジング専用のパッチを有効にする特別なビルド設定
偽陽性ファジング中に見つかったクラッシュのうち、製品版では発生しないもの
決定性同じ入力が常に同じ動作を引き起こすこと(ファジングに不可欠です)

適用する場合

次の場合にこの手法を適用してください。

  • ファザーがチェックサムやハッシュの検証で行き詰まる
  • カバレッジレポートで、検証の先にある大きなコードブロックが到達不能と示される
  • コードが時刻ベースのシードや、その他の非決定的なグローバル状態を使用している
  • 複雑な検証により、有効な入力を生成するのがほぼ不可能である
  • ファザーが同じ検証エラーに何度も到達する

次の場合はこの手法を適用しないでください。

  • 良質なシードコーパスや辞書で障害を克服できる
  • 検証がファザーにとって学習可能なほど単純である(マジックバイトなど)
  • 文法ベースまたは構造を認識するファジングを行っており、検証に対処できる
  • チェックを省略すると偽陽性が多くなりすぎる
  • コードがすでにファジングに適している

クイックリファレンス

タスクC/C++Rust
ファジング用ビルドか確認する#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTIONcfg!(fuzzing)
ファジング中にチェックを省略する#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION return -1; #endifif !cfg!(fuzzing) { return Err(...) }
よくある障害チェックサム、PRNG、時刻ベースのロジックチェックサム、PRNG、時刻ベースのロジック
対応するファザーlibFuzzer, AFL++, LibAFL, honggfuzzcargo-fuzz, libFuzzer

手順

ステップ 1:障害を特定する

ファザーを実行し、カバレッジを分析して到達不能なコードを見つけます。よくあるパターンは次のとおりです。

  1. より深い処理の前にチェックサムやハッシュを検証している箇所を探す
  2. システムのシードを使う rand()、time()、srand() の呼び出しがないか確認する
  3. ほとんどの入力を拒否する検証関数を見つける
  4. 実行ごとに異なるグローバル状態の初期化を特定する

役立つツール:

  • カバレッジレポート(coverage-analysis technique を参照)
  • -fprofile-instr-generate を使用したプロファイリング
  • エントリポイントの手動コード検査
ステップ 2:条件付きコンパイルを追加する

ファジング用ビルドで障害を回避できるように、該当箇所を変更します。

C/C++ の例:

// Before: Hard obstacle
if (checksum != expected_hash) {
    return -1;  // Fuzzer never gets past here
}

// After: Conditional bypass
if (checksum != expected_hash) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return -1;  // Only enforced in production
#endif
}
// Fuzzer can now explore code beyond this check

Rust の例:

// Before: Hard obstacle
if checksum != expected_hash {
    return Err(MyError::Hash);  // Fuzzer never gets past here
}

// After: Conditional bypass
if checksum != expected_hash {
    if !cfg!(fuzzing) {
        return Err(MyError::Hash);  // Only enforced in production
    }
}
// Fuzzer can now explore code beyond this check
ステップ 3:カバレッジの改善を確認する

パッチ適用後、次の手順を行います。

  1. ファジング用インストルメンテーションを有効にして再ビルドする
  2. ファザーを短時間実行する
  3. パッチ適用前のバージョンとカバレッジを比較する
  4. 新しいコードパスが探索されていることを確認する
ステップ 4:誤検知のリスクを評価する

チェックを省略することで、発生し得ないプログラム状態が生じないか検討します。

  • チェック後のコードは、検証済みのプロパティを前提としているでしょうか?
  • 検証を省略すると、本番環境では起こり得ないクラッシュにつながるでしょうか?
  • 暗黙的な状態依存はあるでしょうか?

誤検知が起こりやすい場合は、より対象を絞ったパッチを検討してください(以下の「一般的なパターン」を参照)。

一般的なパターン

パターン:チェックサム検証を回避する

用途: ハッシュ/チェックサムがファザーの進行を妨げている場合

変更前:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
    return ERROR_INVALID_HASH;
}
process_data(data, size);

変更後:

uint32_t computed = hash_function(data, size);
if (computed != expected_checksum) {
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    return ERROR_INVALID_HASH;
#endif
}
process_data(data, size);

誤検知のリスク: 低 — データ処理がチェックサムの正しさに依存しない場合

パターン:PRNG のシードを固定する

用途: 非決定的な乱数状態が再現性を妨げている場合

変更前:

void initialize() {
    srand(time(NULL));  // Different seed each run
}

変更後:

void initialize() {
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
    srand(12345);  // Fixed seed for fuzzing
#else
    srand(time(NULL));
#endif
}

誤検知のリスク: 低 — ファザーは固定シードであらゆるコードパスを探索できます

パターン:慎重に検証を省略する

用途: 検証を省略する必要がある一方で、後続コードに前提条件がある場合

変更前(危険):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;  // Ensures config.x != 0
}
#endif

int32_t result = 100 / config.x;  // CRASH: Division by zero in fuzzing!

変更後(安全):

#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
if (!validate_config(&config)) {
    return -1;
}
#else
// During fuzzing, use safe defaults for failed validation
if (!validate_config(&config)) {
    config.x = 1;  // Prevent division by zero
    config.y = 1;
}
#endif

int32_t result = 100 / config.x;  // Safe in both builds

誤検知のリスク: 軽減済み — 検証を省略する代わりに、安全なデフォルト値を設定します

パターン:複雑な形式検証を回避する

用途: 複数段階の検証により、有効な入力の生成がほぼ不可能になる場合

Rust の例:

// Before: Multiple validation stages
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;
    validate_structure(data)?;
    validate_checksums(data)?;
    validate_crypto_signature(data)?;

    deserialize_message(data)
}

// After: Skip expensive validation during fuzzing
pub fn parse_message(data: &[u8]) -> Result<Message, Error> {
    validate_magic_bytes(data)?;  // Keep cheap checks

    if !cfg!(fuzzing) {
        validate_structure(data)?;
        validate_checksums(data)?;
        validate_crypto_signature(data)?;
    }

    deserialize_message(data)
}

誤検知のリスク: 中 — デシリアライズ処理では、不正なデータを適切に処理する必要があります

高度な使い方

ヒントとコツ
ヒント効果
低コストの検証は維持するマジックバイトやサイズチェックを使えば、あまりコストをかけずにファザーを導けます
PRNG のシードを固定するあらゆるコードパスを探索しながら、動作を決定的にできます
段階的にパッチを適用する障害要因を一度に 1 つずつ取り除き、カバレッジへの影響を測定します
防御的なデフォルト値を追加する検証を省略する際は、安全なフォールバック値を設定します
すべてのパッチを文書化する将来のメンテナーが、ファジング時と本番環境での違いを理解できるようにします
実例

OpenSSL: FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION を使用して、暗号アルゴリズムの動作を変更します。たとえば、crypto/cmp/cmp_vfy.c では、ファジング中に一部の署名チェックを緩和し、証明書検証ロジックをより深く探索できるようにしています。

ogg crate(Rust): cfg!(fuzzing) を使用して、ファジング中にチェックサム検証を省略します。これにより、ファザーは正しいチェックサムの推測に労力を費やすことなく、オーディオ処理コードを探索できます。

パッチの効果を測定する

パッチの適用後、改善度を定量化します。

  1. 行カバレッジ: llvm-cov または cargo-cov を使って、新たに到達可能になった行を確認します
  2. 基本ブロックカバレッジ: 行カバレッジよりも細かく測定できます
  3. 関数カバレッジ: 新たに到達可能になった関数の数を確認します
  4. コーパスサイズ: ファザーがより多様な入力を生成するか確認します

効果的なパッチでは、通常、カバレッジが 10〜50%以上向上します。

他の手法との組み合わせ

障害要因へのパッチ適用は、次の手法と相性がよいです。

  • コーパスシーディング: 初期解析を通過できる有効な入力を与えます
  • 辞書: ファザーがマジックバイトや一般的な値を学習するのに役立ちます
  • 構造を考慮したファジング: 複雑な形式には protobuf や文法定義を使用します
  • ハーネスの改善: より優れたハーネスを使えば、障害要因を完全に回避できる場合があります

アンチパターン

アンチパターン問題正しいアプローチ
すべての検証を一律にスキップする偽陽性が発生し、ファジングが不安定になりますカバレッジを妨げる特定の障害だけをスキップします
リスク評価を行わない偽陽性によって時間が浪費され、実際のバグが見落とされます後続のコードが前提としている条件を分析します
パッチの記録を忘れる今後の保守担当者が変更点を理解できませんパッチが安全である理由を説明するコメントを追加します
測定せずにパッチを適用する効果があったかどうかわかりません適用前後のカバレッジを比較します
パッチを適用しすぎるファジング用のビルドが本番環境から大きく乖離しますビルド間の違いを最小限にします

ツール別のガイダンス

libFuzzer

libFuzzer はコンパイル時に FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION を自動的に定義します。

# C++ compilation
clang++ -g -fsanitize=fuzzer,address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    harness.cc target.cc -o fuzzer

# The macro is usually defined automatically by -fsanitize=fuzzer
clang++ -g -fsanitize=fuzzer,address harness.cc target.cc -o fuzzer

統合のヒント:

  • マクロは自動的に定義されるため、手動での定義は通常不要です
  • マクロの有無は #ifdef で確認します
  • サニタイザーと組み合わせることで、新たに到達可能になったコードのバグを検出できます
AFL++

AFL++ も、コンパイラーラッパーを使用すると FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION を定義します。

# Compilation with AFL++ wrappers
afl-clang-fast++ -g -fsanitize=address target.cc harness.cc -o fuzzer

# The macro is defined automatically by afl-clang-fast

統合のヒント:

  • afl-clang-fast または afl-clang-lto を使用すると、マクロが自動的に定義されます
  • 永続モードのハーネスでは、障害箇所へのパッチ適用が特に効果的です
  • 入力から状態への変換をさらに行うには、AFL_LLVM_LAF_ALL の使用を検討してください
honggfuzz

honggfuzz も、ターゲットのビルド時にこのマクロをサポートします。

# Compilation
hfuzz-clang++ -g -fsanitize=address target.cc harness.cc -o fuzzer

統合のヒント:

  • hfuzz-clang または hfuzz-clang++ ラッパーを使用します
  • 条件付きコンパイルでこのマクロを利用できます
  • honggfuzz のフィードバック駆動型ファジングと組み合わせます
cargo-fuzz(Rust)

cargo-fuzz はビルド時に fuzzing cfg オプションを自動的に設定します。

# Build fuzz target (cfg!(fuzzing) is automatically set)
cargo fuzz build fuzz_target_name

# Run fuzz target
cargo fuzz run fuzz_target_name

統合のヒント:

  • 本番ビルドでの実行時チェックには cfg!(fuzzing) を使用します
  • コンパイル時の条件付きコンパイルには #[cfg(fuzzing)] を使用します
  • fuzzing cfg が設定されるのは cargo fuzz によるビルド時だけで、通常の cargo build では設定されません
  • テスト時には RUSTFLAGS="--cfg fuzzing" で手動で有効にできます
LibAFL

LibAFL は、C/C++ で記述されたターゲット向けに C/C++ マクロをサポートします。

# Compilation
clang++ -g -fsanitize=address -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION \
    target.cc -c -o target.o

統合のヒント:

  • マクロを手動で定義するか、コンパイラーフラグを使用します
  • libFuzzer と同じように機能します
  • LibAFL ベースのカスタムファザーをビルドする場合に便利です

トラブルシューティング

問題原因解決策
パッチ適用後もカバレッジが向上しない障害箇所を誤って特定しています実行をプロファイルして、実際のボトルネックを見つけます
偽陽性のクラッシュが多数発生する後続のコードが前提としている条件があります防御的なデフォルト値または部分的な検証を追加します
コードのコンパイル結果が異なるすべてのビルド設定でマクロが定義されていませんすべてのソースファイルと依存関係でマクロを確認します
ファザーがパッチ適用済みのコードでバグを検出するパッチによって無効な状態が発生しました状態の不変条件に照らしてパッチを確認し、より安全な方法を検討します
本番環境のバグを再現できないビルド間の違いが大きすぎますパッチを最小限にし、状態に不可欠なチェックの検証は維持します

関連スキル

この手法を使用するツール
スキル適用方法
libfuzzerFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION を自動的に定義します
aflppコンパイラーラッパーを介してこのマクロをサポートします
honggfuzz条件付きコンパイルにこのマクロを使用します
cargo-fuzzRust の条件付きコンパイル用に cfg!(fuzzing) を設定します
関連する手法
スキル関連性
fuzz-harness-writingより優れたハーネスによって障害を回避できる場合があります。パッチ適用により、より深い探索が可能になります
coverage-analysisカバレッジを使用して障害を特定し、パッチの効果を測定します
corpus-seedingシードコーパスを使うと、パッチを適用せずに障害を乗り越えられる場合があります
dictionary-generationディクショナリはマジックバイトには役立ちますが、チェックサムや複雑な検証には役立ちません

リソース

主な外部リソース

OpenSSL のファジングに関するドキュメント OpenSSL のファジング基盤は、FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION の大規模な使用例を示しています。このプロジェクトでは、このマクロを使用して暗号検証、証明書の解析、その他のセキュリティ上重要なコードパスを変更し、プロダクション環境での正確性を維持しながら、より深いファジングを可能にしています。

libFuzzer のフラグに関するドキュメント libFuzzer に関する LLVM の公式ドキュメントです。ファザーがコンパイラマクロをどのように定義するか、また、それらを効果的に使用する方法について説明しています。サニタイザーおよびカバレッジ計測との統合についても解説しています。

Rust の cfg 属性リファレンス cfg!(fuzzing) や cfg!(test) など、Rust の条件付きコンパイルに関する完全なリファレンスです。コンパイル時と実行時の条件付きコンパイルの違いや、ベストプラクティスについて説明しています。

コメント