- カバレッジガイドによるファズテストのプロセス
- サポートされるファジングエンジンと言語
- カバレッジガイドファズテストのステータスの確認
- カバレッジガイドファズテストの有効化
- 出力
- コーパス・レジストリ
- カバレッジガイドファズテストレポート
- カバレッジガイドファズテストの期間
- FIPS対応バイナリ
- オフライン環境
- 脆弱性との対話
- トラブルシューティング
カバレッジガイドファズテスト
カバレッジガイドファズテストは、あなたのアプリケーションのインスツルメンテーションされたバージョンにランダムな入力を送り、予期しない振る舞いを引き起こそうとします。そのような振る舞いは、アドレスが対処すべきバグであることを示します。GitLab では、カバレッジガイドファズテストをパイプラインに追加することができます。これにより、他のQAプロセスでは見逃してしまうようなバグや潜在的なセキュリティ問題を発見することができます。
GitLab Secureの他のセキュリティスキャナとあなた自身のテストプロセスに加えて、ファズテストを使うことをお勧めします。GitLab CI/CDを使用している場合は、CI/CDワークフローの一部としてカバレッジガイドファズテストを実行することができます。
概要については、カバレッジファジングをご覧ください。
カバレッジガイドによるファズテストのプロセス
ファズテストのプロセス
- ターゲットアプリケーションをコンパイルします。
-
gitlab-cov-fuzz
ツールを使用して、インスツルメンテッド・アプリケーションを実行します。 - ファザーが出力した例外情報を解析します。
-
コーパスをどちらかからダウンロードします:
- これまでのパイプライン。
-
COVFUZZ_USE_REGISTRY
をtrue
に設定すると、コーパスのレジストリ。
- 以前のパイプラインからクラッシュイベントをダウンロードします。
- 解析されたクラッシュ イベントとデータを
gl-coverage-fuzzing-report.json
ファイルに出力します。 - コーパスの更新
- ジョブのパイプラインで。
-
COVFUZZ_USE_REGISTRY
がtrue
に設定されている場合は、コーパスのレジストリで。
カバレッジガイドによるファズテストの結果は、CI/CDパイプラインで利用可能です。
サポートされるファジングエンジンと言語
指定した言語のテストには、以下のファジングエンジンを使用できます。
対応言語 | ファジングエンジン | 物件例 |
---|---|---|
C/C++ | libFuzzer | c-cpp-example |
Go | go-fuzz (libFuzzer サポート) | go-fuzzingサンプル |
Swift | libFuzzer | swift-fuzzing-example |
Rust | cargo-fuzz (libFuzzer サポート) | Rust-fuzzing-example |
Java (Maven のみ)1 | Javafuzz(推奨) | javafuzz-fuzzing-example |
Java | JQF(好ましくない) | jqf-ファジングの例 |
ジャバスクリプト | jsfuzz | jsfuzz-ファジングの例 |
Python | pythonfuzz | pythonfuzz-fuzzing-example |
AFL (AFLの上で動作する任意の言語) | AFL | afl-fuzzing-example |
- Gradle のサポートはissue 409764 で計画されています。
カバレッジガイドファズテストのステータスの確認
カバレッジガイドファズテストのステータスを確認します:
- 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
- セキュア > セキュリティ設定を選択します。
-
Coverage Fuzzingセクションのステータスは次のとおりです:
- 未設定
- 有効
- GitLab Ultimateへのアップグレードを促すプロンプト。
カバレッジガイドファズテストの有効化
カバレッジガイドファズテストを有効にするには、.gitlab-ci.yml
を編集してください:
-
ステージのリストに
fuzz
。 -
アプリケーションがGoで書かれていない場合は、一致するファジングエンジンを使ったDockerイメージを提供してください。例えば
image: python:latest
-
GitLabインストールの一部として提供された
Coverage-Fuzzing.gitlab-ci.yml
テンプレート をインクルードしてください。 -
my_fuzz_target
ジョブをカスタマイズしてください。
カバレッジガイドファジング設定の抜粋例
stages:
- fuzz
include:
- template: Coverage-Fuzzing.gitlab-ci.yml
my_fuzz_target:
extends: .fuzz_base
script:
# Build your fuzz target binary in these steps, then run it with gitlab-cov-fuzz
# See our example repos for how you could do this with any of our supported languages
- ./gitlab-cov-fuzz run --regression=$REGRESSION -- <your fuzz target>
Coverage-Fuzzing
テンプレートは隠されたジョブを含んでおり.fuzz_base
、ファジングターゲット .fuzz_base
ごとに.fuzz_base
拡張する必要があります .fuzz_base
。.fuzz_base
それぞれのファジングターゲットは個別のジョブを持っていなければなりません。 .fuzz_base
たとえば.fuzz_base
、go-fuzzing-exampleプロジェクトには、1つのファジングターゲット用に拡張された1つのジョブが含まれて .fuzz_base
います。
隠されたジョブ.fuzz_base
はいくつかのYAMLキーを使いますが、自分のジョブでオーバーライドしてはいけません。これらのキーを自分のジョブに含める場合、元の内容をコピーしなければなりません:
before_script
artifacts
rules
利用可能な CI/CD 変数
CI/CDパイプラインでカバレッジガイドファズテストを設定するには、以下の変数を使用します。
CI/CD 変数 | 説明 |
---|---|
COVFUZZ_ADDITIONAL_ARGS |
gitlab-cov-fuzz に渡される引数。 内部ファジングエンジンの動作をカスタマイズするために使用します。引数の完全なリストについては、ファジングエンジンのドキュメントをお読みください。 |
COVFUZZ_BRANCH | 長時間実行されるファジングジョブが実行されるブランチ。それ以外のブランチでは、ファジングのリグレッションテストのみが実行されます。デフォルト:リポジトリのデフォルトブランチ。 |
COVFUZZ_SEED_CORPUS | シードコーパスのディレクトリへのパス。デフォルト:空。 |
COVFUZZ_URL_PREFIX | オフライン環境で使用するためにクローンされたgitlab-cov-fuzz リポジトリへのパス。こ の値は、 オ フ ラ イ ン環境を使用す る 場合のみ変更 し て く だ さ い。デフォルト:https://gitlab.com/gitlab-org/security-products/analyzers/gitlab-cov-fuzz/-/raw 。 |
COVFUZZ_USE_REGISTRY | コーパスをGitLabコーパスレジストリに保存するには、true 。この変数がtrue に設定されている場合、変数COVFUZZ_CORPUS_NAME とCOVFUZZ_GITLAB_TOKEN が必要です。デフォルト:false .GitLab 14.8で導入されました。 |
COVFUZZ_CORPUS_NAME | ジョブで使用するコーパスの名前。 GitLab 14.8から導入。 |
COVFUZZ_GITLAB_TOKEN | 個人アクセストークンまたはプロジェクトアクセストークンで設定され、APIの読み書きアクセスが可能な環境変数。GitLab 14.8で導入。 |
シードコーパス
シードコーパスのファイルは手動で更新する必要があります。カバレッジガイドのファズテストのジョブによって更新されたり上書きされたりすることはありません。
出力
各ファジングステップはこれらのアーティファクトを出力します:
-
gl-coverage-fuzzing-report.json
:カバレッジガイドファズテストの詳細とその結果を含むレポーター。 -
artifacts.zip
:このファイルには2つのコンテナがあります:-
corpus
:現在のジョブおよび以前のジョブによって生成されたすべてのテストケースが含まれます。 -
crashes
:現在のジョブが検出したすべてのクラッシュイベントと、以前のジョブで修正されなかったクラッシュイベントが含まれます。
-
JSON レポートファイルは CI/CD パイプラインページからダウンロードできます。詳細については、アーティファクトのダウンロードを参照してください。
コーパス・レジストリ
- GitLab 14.8で導入されました。
- GitLab 14.9 で一般的に利用可能。機能フラグ
corpus_management
とcorpus_management_ui
が削除されました。
コーパスレジストリはコーパスのライブラリです。プロジェクトのレジストリにあるコーパスは、そのプロジェクトのすべてのジョブで利用できます。プロジェクト全体のレジストリは、ジョブごとに1つのコーパスというデフォルトのオプションよりも効率的なコーパス管理方法です。
コーパスレジストリは、パッケージレジストリを使ってプロジェクトのコーパスを保存します。レジストリに保存されたコーパスは、データのインテグレーションを保証するために隠されます。
コーパスをダウンロードすると、コーパスが最初にアップロードされたときのファイル名に関係なく、artifacts.zip
というファイル名になります。このファイルにはコーパスのみが含まれており、CI/CDパイプラインからダウンロードできるアーティファクトファイルとは異なります。また、レポーター以上の権限を持つプロジェクトメンバーは、直接ダウンロードリンクを使用してコーパスをダウンロードすることができます。
コーパスレジストリの詳細を見る
コーパスレジストリの詳細を見るには、以下のリンクをクリックしてください:
- 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
- セキュア > セキュリティ設定を選択します。
- Coverage Fuzzingセクションで、Manage corpusを選択します。
コーパスレジストリにコーパスを作成します。
コーパスレジストリにコーパスを作成するには、次のどちらかを行います:
- パイプラインでコーパスを作成します。
- 既存のコーパスファイルをアップロード
パイプラインでコーパスを作成します。
パイプラインでコーパスを作成するには、以下の手順に従います:
-
.gitlab-ci.yml
ファイルで、my_fuzz_target
ジョブを編集します。 - 以下の変数を設定してください:
-
COVFUZZ_USE_REGISTRY
をtrue
に設定します。 -
COVFUZZ_CORPUS_NAME
をコーパスの名前に設定します。 -
COVFUZZ_GITLAB_TOKEN
に個人アクセストークンの値を設定します。
-
my_fuzz_target
ジョブの実行後、コーパスはCOVFUZZ_CORPUS_NAME
変数で指定された名前でコーパスレジストリに保存されます。コーパスはパイプラインの実行ごとに更新されます。
コーパスファイルのアップロード
既存のコーパスファイルをアップロードするには
- 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
- セキュア > セキュリティ設定を選択します。
- Coverage Fuzzingセクションで、Manage corpusを選択します。
- 新しいコーパスを選択します。
- 各項目を入力してください。
- ファイルのアップロードを選択します。
- Add(追加)を選択します。
これで.gitlab-ci.yml
ファイルでコーパスを参照できるようになりました。COVFUZZ_CORPUS_NAME
変数で使われている値が、アップロードされたコーパスファイルの名前と正確に一致していることを確認してください。
コーパスレジストリに保存されているコーパスを使う場合
コーパスレジストリに保存されているコーパスを使うには、その名前を参照する必要があります。該当するコーパスの名前を確認するには、コーパスレジストリの詳細をご覧ください。
前提条件:
- プロジェクトでカバレッジガイドファズテストを有効にします。
-
.gitlab-ci.yml
ファイルに以下の変数を設定します:-
COVFUZZ_USE_REGISTRY
をtrue
に設定します。 -
COVFUZZ_CORPUS_NAME
をコーパスの名前に設定します。 -
COVFUZZ_GITLAB_TOKEN
に個人アクセストークンの値を設定します。
-
カバレッジガイドファズテストレポート
GitLab 13.3からExperimentとして導入されました。
gl-coverage-fuzzing-report.json
ファイルのフォーマットの詳細については、スキーマを読んでください。
カバレッジガイドファジングレポートの例:
{
"version": "v1.0.8",
"regression": false,
"exit_code": -1,
"vulnerabilities": [
{
"category": "coverage_fuzzing",
"message": "Heap-buffer-overflow\nREAD 1",
"description": "Heap-buffer-overflow\nREAD 1",
"severity": "Critical",
"stacktrace_snippet": "INFO: Seed: 3415817494\nINFO: Loaded 1 modules (7 inline 8-bit counters): 7 [0x10eee2470, 0x10eee2477), \nINFO: Loaded 1 PC tables (7 PCs): 7 [0x10eee2478,0x10eee24e8), \nINFO: 5 files found in corpus\nINFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes\nINFO: seed corpus: files: 5 min: 1b max: 4b total: 14b rss: 26Mb\n#6\tINITED cov: 7 ft: 7 corp: 5/14b exec/s: 0 rss: 26Mb\n=================================================================\n==43405==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000001573 at pc 0x00010eea205a bp 0x7ffee0d5e090 sp 0x7ffee0d5e088\nREAD of size 1 at 0x602000001573 thread T0\n #0 0x10eea2059 in FuzzMe(unsigned char const*, unsigned long) fuzz_me.cc:9\n #1 0x10eea20ba in LLVMFuzzerTestOneInput fuzz_me.cc:13\n #2 0x10eebe020 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:556\n #3 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n #4 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n #5 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n #6 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n #7 0x10eedaf82 in main FuzzerMain.cpp:19\n #8 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\n0x602000001573 is located 0 bytes to the right of 3-byte region [0x602000001570,0x602000001573)\nallocated by thread T0 here:\n #0 0x10ef92cfd in wrap__Znam+0x7d (libclang_rt.asan_osx_dynamic.dylib:x86_64+0x50cfd)\n #1 0x10eebdf31 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) FuzzerLoop.cpp:541\n #2 0x10eebd765 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) FuzzerLoop.cpp:470\n #3 0x10eebf966 in fuzzer::Fuzzer::MutateAndTestOne() FuzzerLoop.cpp:698\n #4 0x10eec0665 in fuzzer::Fuzzer::Loop(std::__1::vector\u003cfuzzer::SizedFile, fuzzer::fuzzer_allocator\u003cfuzzer::SizedFile\u003e \u003e\u0026) FuzzerLoop.cpp:830\n #5 0x10eead0cd in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) FuzzerDriver.cpp:829\n #6 0x10eedaf82 in main FuzzerMain.cpp:19\n #7 0x7fff684fecc8 in start+0x0 (libdyld.dylib:x86_64+0x1acc8)\n\nSUMMARY: AddressSanitizer: heap-buffer-overflow fuzz_me.cc:9 in FuzzMe(unsigned char const*, unsigned long)\nShadow bytes around the buggy address:\n 0x1c0400000250: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n 0x1c0400000260: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n 0x1c0400000270: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n 0x1c0400000280: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n 0x1c0400000290: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa\n=\u003e0x1c04000002a0: fa fa fd fa fa fa fd fa fa fa fd fa fa fa[03]fa\n 0x1c04000002b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n 0x1c04000002c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n 0x1c04000002d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n 0x1c04000002e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\n 0x1c04000002f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa\nShadow byte legend (one shadow byte represents 8 application bytes):\n Addressable: 00\n Partially addressable: 01 02 03 04 05 06 07 \n Heap left redzone: fa\n Freed heap region: fd\n Stack left redzone: f1\n Stack mid redzone: f2\n Stack right redzone: f3\n Stack after return: f5\n Stack use after scope: f8\n Global redzone: f9\n Global init order: f6\n Poisoned by user: f7\n Container overflow: fc\n Array cookie: ac\n Intra object redzone: bb\n ASan internal: fe\n Left alloca redzone: ca\n Right alloca redzone: cb\n Shadow gap: cc\n==43405==ABORTING\nMS: 1 EraseBytes-; base unit: de3a753d4f1def197604865d76dba888d6aefc71\n0x46,0x55,0x5a,\nFUZ\nartifact_prefix='./crashes/'; Test unit written to ./crashes/crash-0eb8e4ed029b774d80f2b66408203801cb982a60\nBase64: RlVa\nstat::number_of_executed_units: 122\nstat::average_exec_per_sec: 0\nstat::new_units_added: 0\nstat::slowest_unit_time_sec: 0\nstat::peak_rss_mb: 28",
"scanner": {
"id": "libFuzzer",
"name": "libFuzzer"
},
"location": {
"crash_address": "0x602000001573",
"crash_state": "FuzzMe\nstart\nstart+0x0\n\n",
"crash_type": "Heap-buffer-overflow\nREAD 1"
},
"tool": "libFuzzer"
}
]
}
カバレッジガイドファズテストの期間
カバレッジガイドファズテストに使用できる期間は次のとおりです:
- 10 分間 (デフォルト):デフォルトのブランチに推奨します。
- 60 分間:開発ブランチやマージリクエストに推奨します。時間が長いほど、カバレッジが大きくなります。
COVFUZZ_ADDITIONAL_ARGS
変数で値--regression=true
を設定します。
完全な例については、Go カバレッジガイドファジングの例を読んでください。
連続カバレッジガイドファズテスト
メインパイプラインをブロックすることなく、カバレッジガイド付きファジングジョブを長く実行することも可能です。この設定では、GitLabの親子パイプラインを使います。
このシナリオで推奨されるワークフローは、メインブランチや開発者ブランチでは長時間の非同期ファジングジョブを実行し、その他のブランチやMRでは短時間の同期ファジングジョブを実行するというものです。これにより、コミットごとのパイプラインを迅速に完了させるというニーズと、ファザーがアプリを完全に調査してテストするための多くの時間を確保するというニーズのバランスをとることができます。長時間のファジングジョブは、カバレッジガイドファザーがコードベースのより深いバグを見つけるために通常必要です。
以下は、このワークフローの.gitlab-ci.yml
ファイルの抜粋です。完全な例については、Goファジング例のリポジトリを参照してください:
sync_fuzzing:
variables:
COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=300'
trigger:
include: .covfuzz-ci.yml
strategy: depend
rules:
- if: $CI_COMMIT_BRANCH != 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'
async_fuzzing:
variables:
COVFUZZ_ADDITIONAL_ARGS: '-max_total_time=3600'
trigger:
include: .covfuzz-ci.yml
rules:
- if: $CI_COMMIT_BRANCH == 'continuous_fuzzing' && $CI_PIPELINE_SOURCE != 'merge_request_event'
これは2つのジョブを作成します:
-
sync_fuzzing
:すべてのファズ・ターゲットをブロック設定で短時間実行します。これにより、単純なバグを発見し、MRが新しいバグを導入したり、古いバグを再発生させたりしていないことを確信できます。 -
async_fuzzing
:ブランチ上で実行し、開発サイクルやMRをブロックすることなくコードの深いバグを検出します。
covfuzz-ci.yml
は元の同期的な例と同じです。
FIPS対応バイナリ
GitLab 15.0から、カバレッジファジングのバイナリはLinux x86上でgolang-fips
、暗号バックエンドとしてOpenSSLを使ってコンパイルされています。詳細はGitLab with GoのFIPS対応をご覧ください。
オフライン環境
オフライン環境でカバレッジファジングを使用する場合:
-
gitlab-cov-fuzz
を、オフラインの GitLab インスタンスがアクセスできる非公開リポジトリにクローンします。 -
ファジングのステップごとに、
COVFUZZ_URL_PREFIX
を${NEW_URL_GITLAB_COV_FUZ}/-/raw
に設定します。NEW_URL_GITLAB_COV_FUZ
は、最初のステップで設定した非公開gitlab-cov-fuzz
クローンの URL です。
脆弱性との対話
脆弱性が見つかった後、その脆弱性にアドレスすることができます。マージリクエストウィジェットには脆弱性が一覧表示され、ファジングのアーティファクトをダウンロードするためのボタンがコンテナされています。検出された脆弱性の一つを選択すると、その詳細を見ることができます。
また、グループ、プロジェクト、パイプラインのすべてのセキュリティ脆弱性の概要を表示するセキュリティダッシュボードからも、脆弱性を見ることができます。
脆弱性を選択すると、脆弱性に関する追加情報を提供するモーダルが開きます:
- ステータス:脆弱性のステータス。他のタイプの脆弱性と同様に、カバレッジ・ファジングの脆弱性は、「検出」、「確認」、「却下」、「解決」のいずれかになります。
- プロジェクト:脆弱性が存在するプロジェクト。
- クラッシュの種類:コードのクラッシュや脆弱性のタイプ。これは通常CWE に対応します。
- クラッシュ状態:スタックトレースの正規化バージョンで、クラッシュの最後の3つの関数(ランダムアドレスなし)を含みます。
- スタックトレースのスニペット:クラッシュの詳細を示すスタックトレースの最後の数行。
- 識別子:脆弱性の識別子。CVEまたはCWEに対応します。
- 深刻度(Severity):脆弱性の深刻度。重要(Critical)、高(High)、中(Medium)、低(Low)、情報(Info)、不明(Unknown)のいずれかです。
- スキャナ:脆弱性を検出したスキャナ(Coverage Fuzzingなど)。
- スキャナプロバイダ:スキャンを行ったエンジン。Coverage Fuzzingの場合、Supported fuzzing engines and languagesに記載されているエンジンのどれでもかまいません。
トラブルシューティング
エラーUnable to extract corpus folder from artifacts zip file
このエラーメッセージが表示され、COVFUZZ_USE_REGISTRY
がtrue
に設定されている場合、アップロードされたコーパスファイルがcorpus
という名前のフォルダに解凍されることを確認してください。
エラー400 Bad request - Duplicate package is not allowed
COVFUZZ_USE_REGISTRY
をtrue
に設定してファジングジョブを実行したときにこのエラーメッセージが表示された場合は、重複が許可されていることを確認してください。詳細については、Genericパッケージの重複を参照してください。