fuzzer について
コード監査とテスト向けのファジングスキルです。コードベース全体の監査で対象を選び、ハーネスの作成、ビルド、実行、結果報告までを案内します。C/C++、Rust、Go、バイナリ向けの手順を含みます。
主な機能
- 01全コードを監査して疑わしい箇所を順位付け
- 02バグの種類に応じたハーネス作成
- 03C/C++、Rust、Goのビルド手順
- 04ファジング実行とクラッシュや結果の報告
活用シーン
- 01長さ関連のリスクを調べる対象の選定
- 02対象関数向けのファジングハーネス作成
- 03キャンペーンを実行して結果を整理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ workersio/ skills --skill fuzzerインストール後、Claude Code で/fuzzerを入力して読み込まれたか確認します。
詳細
ファザー スキル
使用するタイミング
ターゲットのファジング、ファジングによるメモリ/整数のバグの発見、ファズハーネスの作成、またはコードベースに対するファジングキャンペーンの実行を依頼された場合に呼び出します。
ワークフロー
ステップ 1 — 監査(必須 — 例外なく必ず最初に実行)
ハーネスを作成する前に、必ず audit-context-building スキルを呼び出してください。
すでにコードを理解していると思っていても、このステップを省略してはいけません。
目標:順位付けを行う前に、コードベース全体、つまりすべてのソースファイルを読みます。1つのディレクトリで最初に疑わしい場所を見つけた時点で止めないでください。すべてのモジュールを網羅してください。
特に以下を確認してください。
size_t → intまたはsize_t → uint32_tの縮小キャスト- チェックされていない長さの算術演算(サイズに対する加算、乗算)
- 攻撃者が制御可能な
size_t/長さのパラメーターを受け取る公開 API 関数
出力:コードベース全体から見つかった、疑わしい箇所の順位付きリスト(file:line 形式)。
ハーネスの対象には、最上位の候補を選びます。
ステップ 2 — ハーネスを作成する
監査で第 1 位に順位付けされた正確な file:line を必ず対象にしてください。独自の判断で別の関数、コードパス、または「より興味深い」箇所を対象にしてはいけません。対象は監査で決定します。あなたはそれを実装します。
- 監査で提示された呼び出しパスを読みます
- その呼び出しパス内の正確な関数を呼び出します
- 特定されたバグのクラスに基づいて、以下のパターンを選びます
ステップ 1 で特定されたバグのクラスに基づいて、パターンを選びます。
算術オーバーフロー(size_t→int の累積、サイズに対するチェックなしの加算):
バグはバッファーの内容ではなく、算術演算にあります。ファズバイトから uint32_t のサイズ値を抽出し、それをそのまま渡します。データポインターには、1 バイトの静的スタブを使用します。渡すバッファーの実際のサイズからサイズ値を導出してはいけません。
#include <stddef.h>
#include <stdint.h>
#include <string.h>
static const uint8_t kStub[1] = {0};
/* Required by libAFLDriver.a — must be present or link fails */
int LLVMFuzzerInitialize(int *argc, char ***argv) {
(void)argc; (void)argv;
return 0;
}
void LLVMFuzzerCleanup(void) {}
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
uint8_t n = data[0] % MAX_CALLS;
if (n == 0 || size < 1 + (size_t)n * 4) return 0;
TargetState *s = TargetState_Create();
for (uint8_t i = 0; i < n; i++) {
uint32_t claimed;
memcpy(&claimed, data + 1 + i * 4, 4);
target_fn(s, (size_t)claimed, kStub); /* claimed drives the arithmetic */
}
TargetState_Destroy(s);
return 0;
}
分割入力(API が設定/サイズのパラメーターとデータペイロードを受け取る場合):
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < N) return 0;
/* bytes [0..N-1] → config / mode / count */
/* bytes [N..] → payload */
target_fn(config, data + N, size - N);
return 0;
}
直接呼び出し(単純なバッファーと長さ):
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 1) return 0;
target_fn(data, size);
return 0;
}
Rust:
fuzz_target!(|data: &[u8]| { target_function(data); });
Go:
func FuzzTarget(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) { target_function(data) })
}
ステップ 3 — ビルド
ビルドする前に、AFL++ の場所を確認します。
which afl-clang-fast || find /usr/local /opt/homebrew /tmp -name afl-clang-fast 2>/dev/null
見つからない場合は、インストールします。
brew install afl++ # macOS
apt-get install afl++ # Debian/Ubuntu
AFL++ を使用する C/C++:
AFL=$(which afl-clang-fast)
AFLDRIVER=$(dirname $(dirname $AFL))/lib/afl/libAFLDriver.a
# Compile target sources into instrumented static library
mkdir -p build
find <src-dir> -name "*.c" | while read f; do
$AFL \
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer \
<include-flags> -c "$f" -o "build/$(basename $f .c).o"
done
ar rcs build/libtarget.a build/*.o
# Compile harness + link
$AFL \
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer \
harness.c build/libtarget.a $AFLDRIVER \
-o build/fuzzer
Rust:
cargo fuzz build <target_name>
ステップ 4 — 実行
AFL++ を使用する C/C++:
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:symbolize=0 \
AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \
$(which afl-fuzz) \
-i corpus/ \
-o findings/ \
-- build/fuzzer
Rust:
cargo fuzz run <target_name> corpus/
Go:
go test -fuzz=FuzzTarget -fuzztime=12h ./...
ステップ 5 — 報告
findings/default/crashes/ にファイルがある場合は、以下を報告します。
CRASH FOUND
Input: findings/default/crashes/id:000000,...
Signal: sig:06 (SIGABRT = sanitizer) or sig:11 (SIGSEGV)
Reproduce (with symbolized output):
UBSAN_OPTIONS=print_stacktrace=1 \
ASAN_OPTIONS=detect_leaks=0:print_stacktrace=1 \
./build/fuzzer findings/default/crashes/id:000000,...
Sanitizer output:
<paste UBSan/ASan stacktrace here>
Root cause: <file>:<line> — <one sentence description>
制限時間内にクラッシュが発生しなかった場合は、見つかったパスとコーパス内のユニークな入力を報告します。
ハーネスのルール
| ルール | 理由 |
|---|---|
| 常に 0 を返す | ハーネス自体からアボートしないため |
exit() を呼び出さない | ファザーのプロセスを終了させるため |
| あらゆるサイズの入力を処理する | ファザーは空/極小/巨大な入力を生成するため |
| 高速にする — ログを出力しない | 目標は 100~1000 件以上の実行/秒 |
| 同じ入力には同じ出力を返す | クラッシュの再現には決定性が必要なため |
| 呼び出しごとにすべてのリソースを解放する | 数百万回の実行によるメモリ枯渇を防ぐため |
| グローバル状態をリセットする | 各イテレーションを分離するため |
ツールの選択
| ターゲット | ファザー | 備考 |
|---|---|---|
| C/C++ | AFL++(afl-clang-fast) | 最適なカバレッジ計測 |
| Rust | cargo-fuzz | 内部で libFuzzer API を使用 |
| Go | go test -fuzz | ネイティブ対応で、追加ツールは不要 |
| 任意のバイナリ | AFL++ ブラックボックス(afl-fuzz @@) | ソースコードは不要 |
| カスタム/研究用 | LibAFL | モジュール型の Rust ファジングライブラリ |