Go言語入門:Fuzzing -Vol.7-

スポンサーリンク
Go言語入門:Fuzzing -Vol.7- ノウハウ
Go言語入門:Fuzzing -Vol.7-
この記事は約11分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

本日は、Go言語のFuzzingついて解説しています。

スポンサーリンク

背景

Goでテストを書いていると、「Fuzzing」という言葉を目にすることがあるかもしれません。公式ドキュメントを開いてみたものの、英語で書かれていて「coverage guidance」「edge cases」といった専門用語が並び、正直なところ「何をするものなのか、よくわからない…」と感じた方も多いのではないでしょうか。

実は私も最初は同じでした。「自動テスト」「入力を操作」「セキュリティ脆弱性の発見」と聞いても、具体的にどう使えばいいのか、なぜ必要なのかがピンと来なかったんです。

そこでこの記事では、Go公式ドキュメントの「Fuzzing」について、丁寧な日本語訳はもちろん、初心者の方にもわかるように補足説明や具体例を交えながら解説していきます。Fuzzingは一見難しそうに見えますが、実は「人間が思いつかないようなテストケースを自動で試してくれる便利な仕組み」なんです。

テストコードをより強固にしたい、セキュリティに配慮したコードを書きたいと考えている方にとって、Fuzzingは強力な味方になってくれます。一緒に学んでいきましょう!

カスタム設定

デフォルトのgoコマンド設定は、ファジングのほとんどのユースケースで機能するはずです。そのため、通常、コマンドライン上でのファジングの実行は次のようになります:

$ go test -fuzz={FuzzTestName}

ただし、goコマンドは、ファジングを実行する際にいくつかの設定を提供しています。これらはcmd/goパッケージのドキュメントに記載されています。

いくつかを強調すると:

  • -fuzztime: ファズターゲットが終了する前に実行される合計時間または反復回数、デフォルトは無期限。
  • -fuzzminimizetime: 各最小化の試行中にファズターゲットが実行される時間または反復回数、デフォルトは60秒。ファジング時に-fuzzminimizetime 0を設定することで、最小化を完全に無効化できます。
  • -parallel: 一度に実行されるファジングプロセスの数、デフォルトは$GOMAXPROCS。現在、ファジング中に-cpuを設定しても効果はありません。

解説

ファジングの動作をカスタマイズする

基本的にはgo test -fuzz=FuzzTestNameだけで十分ですが、状況に応じて細かく調整できる設定があります。実際の使用例とともに見ていきましょう。


1. -fuzztime: 実行時間を制限する

デフォルト: 無期限(手動で止めるまで永遠に実行)

基本的な使い方

# 30秒間だけファジング
go test -fuzz=FuzzReverse -fuzztime=30s

# 5分間ファジング
go test -fuzz=FuzzReverse -fuzztime=5m

# 1時間ファジング
go test -fuzz=FuzzReverse -fuzztime=1h

実行回数で指定

時間ではなく、実行回数で制限することもできます:

# 10万回実行したら停止
go test -fuzz=FuzzReverse -fuzztime=100000x

# 100万回実行したら停止
go test -fuzz=FuzzReverse -fuzztime=1000000x

実行例:

$ go test -fuzz=FuzzReverse -fuzztime=10s
fuzz: elapsed: 3s, execs: 325017 (108336/sec), new interesting: 11 (total: 202)
fuzz: elapsed: 6s, execs: 680218 (118402/sec), new interesting: 12 (total: 203)
fuzz: elapsed: 9s, execs: 1039901 (119895/sec), new interesting: 19 (total: 210)
--- PASS: FuzzReverse (10.00s)  ← 10秒で自動停止
PASS

使い分け

時間で指定する場合:

# CI/CDパイプラインで: 3分以内に終わらせる
go test -fuzz=FuzzParse -fuzztime=3m

# 開発中: サクッと確認
go test -fuzz=FuzzValidate -fuzztime=10s

# 週末: じっくり探索
go test -fuzz=FuzzProcess -fuzztime=24h

実行回数で指定する場合:

# 特定の実行回数をベンチマークしたい
go test -fuzz=FuzzEncode -fuzztime=1000000x

# 軽量なチェック
go test -fuzz=FuzzDecode -fuzztime=10000x

2. -fuzzminimizetime: 最小化時間を調整する

デフォルト: 60秒

最小化とは?(復習)

バグを見つけたとき、できるだけ小さい入力に変換する処理のことです。

バグを引き起こす入力:
"aaaaaaaaaaaaa🎉bbbbbbbbbb"
     ↓ 最小化(60秒かけて)
"🎉"

最小化時間を変更する

# 最小化に30秒だけ使う(速く終わらせたい)
go test -fuzz=FuzzReverse -fuzzminimizetime=30s

# 最小化に2分かける(より小さい入力を探したい)
go test -fuzz=FuzzReverse -fuzzminimizetime=2m

# 最小化を無効化(最速)
go test -fuzz=FuzzReverse -fuzzminimizetime=0

実行例: 最小化あり(デフォルト)

$ go test -fuzz=FuzzReverse -fuzztime=10s
fuzz: elapsed: 5s, execs: 542319 (108463/sec)
--- FAIL: FuzzReverse (5.23s)
    fuzz: elapsed: 5s, minimizing  ← 最小化開始
    fuzz: elapsed: 10s, minimizing
    fuzz: elapsed: 30s, minimizing
    fuzz: elapsed: 60s, minimization done  ← 60秒後に完了
    reverse_test.go:15: Reverse(Reverse("🎉")) = "", want "🎉"
    
    Failing input written to testdata/fuzz/FuzzReverse/a8f3d...
FAIL

実行例: 最小化無効

$ go test -fuzz=FuzzReverse -fuzztime=10s -fuzzminimizetime=0
fuzz: elapsed: 5s, execs: 542319 (108463/sec)
--- FAIL: FuzzReverse (5.01s)
    reverse_test.go:15: Reverse(Reverse("aaaa🎉bbbb")) = "", want "aaaa🎉bbbb"
    
    Failing input written to testdata/fuzz/FuzzReverse/b7e2a...
FAIL

↑ 最小化されていないので、入力が長いままです。

いつ最小化を無効にするべき?

最小化を無効にする(-fuzzminimizetime=0:

  • ✅ とにかく速くバグを報告したい
  • ✅ 大量のバグが見つかることが予想される
  • ✅ CI/CDで時間制限がある

最小化を有効にする(デフォルトまたは長めに):

  • ✅ バグの根本原因を理解しやすくしたい
  • ✅ 開発者に分かりやすい入力を提供したい
  • ✅ デバッグしやすいテストケースが欲しい

:

# CI: 速く終わらせる
go test -fuzz=FuzzAll -fuzztime=1m -fuzzminimizetime=0

# 開発: じっくりデバッグ
go test -fuzz=FuzzProblem -fuzztime=10m -fuzzminimizetime=2m

3. -parallel: 並列実行数を制御する

デフォルト: $GOMAXPROCS(通常はCPUコア数)

並列実行の仕組み

ファジングは複数のワーカー(プロセス)が同時に入力を生成・テストします。

8コアCPU → 8つのワーカーが並列実行
    ↓
ワーカー1: テスト実行中...
ワーカー2: テスト実行中...
ワーカー3: テスト実行中...
...
ワーカー8: テスト実行中...

並列数を変更する

# 2つのワーカーだけ使う(CPUリソースを節約)
go test -fuzz=FuzzReverse -parallel=2

# 16ワーカー使う(強力なマシンで)
go test -fuzz=FuzzReverse -parallel=16

# 1ワーカーのみ(デバッグ時)
go test -fuzz=FuzzReverse -parallel=1

実行例:

$ go test -fuzz=FuzzReverse -parallel=4
fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 4 workers
                                                                          ↑ 4ワーカー
$ go test -fuzz=FuzzReverse -parallel=16
fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 16 workers
                                                                          ↑ 16ワーカー

使い分けガイド

並列数を減らす(-parallel=1-parallel=2:

  • ✅ CPUリソースを他のタスクに使いたい
  • ✅ デバッグ中で、出力を見やすくしたい
  • ✅ メモリ消費を抑えたい

並列数を増やす(-parallel=16以上):

  • ✅ 強力なサーバーで実行している
  • ✅ できるだけ多くの実行回数をこなしたい
  • ✅ 時間制限内で最大限バグを見つけたい

実例:

# ノートPCで開発中: 負荷を軽く
go test -fuzz=FuzzParse -parallel=2 -fuzztime=1m

# 専用サーバーで夜間実行: フルパワー
go test -fuzz=FuzzParse -parallel=32 -fuzztime=8h

# デバッグ: 出力を追いやすく
go test -fuzz=FuzzProblem -parallel=1 -fuzztime=30s

-cpuフラグについての注意

重要: 通常のテストでは-cpuフラグが使えますが、ファジングでは無効です。

# ❌ ファジングでは効果なし
go test -fuzz=FuzzReverse -cpu=4

# ✅ 代わりに-parallelを使う
go test -fuzz=FuzzReverse -parallel=4

実践的な組み合わせ例

シナリオ1: CI/CDパイプライン

制限時間3分、最小化なし、中程度の並列実行:

go test -fuzz=FuzzAll -fuzztime=3m -fuzzminimizetime=0 -parallel=4

理由:

  • 時間制限でパイプラインを遅延させない
  • 最小化なしで速く結果を出す
  • 並列数を抑えてCIリソースを節約

シナリオ2: 開発中のクイックチェック

10秒だけ、デフォルト設定:

go test -fuzz=FuzzValidate -fuzztime=10s

理由:

  • シンプルで覚えやすい
  • 素早くフィードバック

シナリオ3: 徹底的なセキュリティテスト

長時間、しっかり最小化、フルパワー:

go test -fuzz=FuzzParser -fuzztime=12h -fuzzminimizetime=5m -parallel=16

理由:

  • 12時間かけてじっくり探索
  • 5分かけて最小入力を見つける
  • 16ワーカーで効率的に実行

シナリオ4: デバッグ中

短時間、単一ワーカー、最小化なし:

go test -fuzz=FuzzBuggy -fuzztime=30s -fuzzminimizetime=0 -parallel=1

理由:

  • 出力が追いやすい
  • 再現性が高い
  • CPUを占有しない

設定の確認方法

現在の環境のGOMAXPROCSを確認:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Println("GOMAXPROCS:", runtime.GOMAXPROCS(0))
}
$ go run check.go
GOMAXPROCS: 8  ← デフォルトで8ワーカー使用

まとめ

主要な設定オプション:

オプションデフォルト用途
-fuzztime無期限実行時間・回数の制限
-fuzzminimizetime60秒最小化にかける時間
-parallelCPUコア数並列ワーカー数

クイックリファレンス:

# 基本
go test -fuzz=FuzzName

# CI/CD向け
go test -fuzz=FuzzName -fuzztime=3m -fuzzminimizetime=0 -parallel=4

# 開発中
go test -fuzz=FuzzName -fuzztime=10s

# 徹底的テスト
go test -fuzz=FuzzName -fuzztime=8h -parallel=16

# デバッグ
go test -fuzz=FuzzName -fuzztime=1m -parallel=1 -fuzzminimizetime=0

覚えておくべきこと:

  • ✅ 大抵の場合、デフォルトで十分
  • ✅ CI/CDでは時間制限を設定
  • ✅ デバッグ時は-parallel=1が便利
  • -cpuではなく-parallelを使う

これらの設定を使いこなすことで、状況に応じて効率的にファジングできます!

おわりに 

本日は、Go言語のFuzzingについて解説しました。

よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました