カバレッジガイドファズテスト

カバレッジガイドファズテストは、あなたのアプリケーションのインスツルメンテーションされたバージョンにランダムな入力を送り、予期しない振る舞いを引き起こそうとします。そのような振る舞いは、アドレスが対処すべきバグであることを示します。GitLab では、カバレッジガイドファズテストをパイプラインに追加することができます。これにより、他のQAプロセスでは見逃してしまうようなバグや潜在的なセキュリティ問題を発見することができます。

GitLab Secureの他のセキュリティスキャナとあなた自身のテストプロセスに加えて、ファズテストを使うことをお勧めします。GitLab CI/CDを使用している場合は、CI/CDワークフローの一部としてカバレッジガイドファズテストを実行することができます。

概要については、カバレッジファジングをご覧ください。

カバレッジガイドによるファズテストのプロセス

ファズテストのプロセス

  1. ターゲットアプリケーションをコンパイルします。
  2. gitlab-cov-fuzz ツールを使用して、インスツルメンテッド・アプリケーションを実行します。
  3. ファザーが出力した例外情報を解析します。
  4. コーパスをどちらかからダウンロードします:
  5. 以前のパイプラインからクラッシュイベントをダウンロードします。
  6. 解析されたクラッシュ イベントとデータをgl-coverage-fuzzing-report.json ファイルに出力します。
  7. コーパスの更新
    • ジョブのパイプラインで。
    • COVFUZZ_USE_REGISTRYtrueに設定されている場合は、コーパスのレジストリで。

カバレッジガイドによるファズテストの結果は、CI/CDパイプラインで利用可能です。

サポートされるファジングエンジンと言語

指定した言語のテストには、以下のファジングエンジンを使用できます。

対応言語ファジングエンジン物件例
C/C++libFuzzerc-cpp-example
Gogo-fuzz (libFuzzer サポート)go-fuzzingサンプル
SwiftlibFuzzerswift-fuzzing-example
Rustcargo-fuzz (libFuzzer サポート)Rust-fuzzing-example
Java (Maven のみ)1 Javafuzz(推奨)javafuzz-fuzzing-example
Java JQF(好ましくない)jqf-ファジングの例
ジャバスクリプトjsfuzzjsfuzz-ファジングの例
Pythonpythonfuzzpythonfuzz-fuzzing-example
AFL (AFLの上で動作する任意の言語)AFLafl-fuzzing-example
  1. Gradle のサポートはissue 409764 で計画されています。

カバレッジガイドファズテストのステータスの確認

カバレッジガイドファズテストのステータスを確認します:

  1. 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
  2. セキュア > セキュリティ設定を選択します。
  3. Coverage Fuzzingセクションのステータスは次のとおりです:
    • 未設定
    • 有効
    • GitLab Ultimateへのアップグレードを促すプロンプト。

カバレッジガイドファズテストの有効化

カバレッジガイドファズテストを有効にするには、.gitlab-ci.yml を編集してください:

  1. ステージのリストにfuzz

  2. アプリケーションがGoで書かれていない場合は、一致するファジングエンジンを使ったDockerイメージを提供してください。例えば

    image: python:latest
    
  3. GitLabインストールの一部として提供されたCoverage-Fuzzing.gitlab-ci.yml テンプレートインクルードしてください。

  4. 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_basego-fuzzing-exampleプロジェクトには、1つのファジングターゲット用に拡張された1つのジョブが含まれて .fuzz_baseいます。

隠されたジョブ.fuzz_base はいくつかのYAMLキーを使いますが、自分のジョブでオーバーライドしてはいけません。これらのキーを自分のジョブに含める場合、元の内容をコピーしなければなりません:

  • before_script
  • artifacts
  • rules

利用可能な CI/CD 変数

CI/CDパイプラインでカバレッジガイドファズテストを設定するには、以下の変数を使用します。

caution
GitLab セキュリティスキャンツールのすべてのカスタマイズは、デフォルトブランチに変更をマージする前にマージリクエストでテストしてください。これを怠ると、多数の偽陽性を含む予期しない結果をもたらす可能性があります。
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_NAMECOVFUZZ_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 パイプラインページからダウンロードできます。詳細については、アーティファクトのダウンロードを参照してください。

コーパス・レジストリ

コーパスレジストリはコーパスのライブラリです。プロジェクトのレジストリにあるコーパスは、そのプロジェクトのすべてのジョブで利用できます。プロジェクト全体のレジストリは、ジョブごとに1つのコーパスというデフォルトのオプションよりも効率的なコーパス管理方法です。

コーパスレジストリは、パッケージレジストリを使ってプロジェクトのコーパスを保存します。レジストリに保存されたコーパスは、データのインテグレーションを保証するために隠されます。

コーパスをダウンロードすると、コーパスが最初にアップロードされたときのファイル名に関係なく、artifacts.zip というファイル名になります。このファイルにはコーパスのみが含まれており、CI/CDパイプラインからダウンロードできるアーティファクトファイルとは異なります。また、レポーター以上の権限を持つプロジェクトメンバーは、直接ダウンロードリンクを使用してコーパスをダウンロードすることができます。

コーパスレジストリの詳細を見る

コーパスレジストリの詳細を見るには、以下のリンクをクリックしてください:

  1. 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
  2. セキュア > セキュリティ設定を選択します。
  3. Coverage Fuzzingセクションで、Manage corpusを選択します。

コーパスレジストリにコーパスを作成します。

コーパスレジストリにコーパスを作成するには、次のどちらかを行います:

  • パイプラインでコーパスを作成します。
  • 既存のコーパスファイルをアップロード

パイプラインでコーパスを作成します。

パイプラインでコーパスを作成するには、以下の手順に従います:

  1. .gitlab-ci.yml ファイルで、my_fuzz_target ジョブを編集します。
  2. 以下の変数を設定してください:
    • COVFUZZ_USE_REGISTRYtrueに設定します。
    • COVFUZZ_CORPUS_NAME をコーパスの名前に設定します。
    • COVFUZZ_GITLAB_TOKEN に個人アクセストークンの値を設定します。

my_fuzz_target ジョブの実行後、コーパスはCOVFUZZ_CORPUS_NAME 変数で指定された名前でコーパスレジストリに保存されます。コーパスはパイプラインの実行ごとに更新されます。

コーパスファイルのアップロード

既存のコーパスファイルをアップロードするには

  1. 左のサイドバーで「検索」または「移動」を選択してあなたのプロジェクトを検索します。
  2. セキュア > セキュリティ設定を選択します。
  3. Coverage Fuzzingセクションで、Manage corpusを選択します。
  4. 新しいコーパスを選択します。
  5. 各項目を入力してください。
  6. ファイルのアップロードを選択します。
  7. Add(追加)を選択します。

これで.gitlab-ci.yml ファイルでコーパスを参照できるようになりました。COVFUZZ_CORPUS_NAME 変数で使われている値が、アップロードされたコーパスファイルの名前と正確に一致していることを確認してください。

コーパスレジストリに保存されているコーパスを使う場合

コーパスレジストリに保存されているコーパスを使うには、その名前を参照する必要があります。該当するコーパスの名前を確認するには、コーパスレジストリの詳細をご覧ください。

前提条件:

  1. .gitlab-ci.yml ファイルに以下の変数を設定します:
    • COVFUZZ_USE_REGISTRYtrueに設定します。
    • 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つのジョブを作成します:

  1. sync_fuzzing:すべてのファズ・ターゲットをブロック設定で短時間実行します。これにより、単純なバグを発見し、MRが新しいバグを導入したり、古いバグを再発生させたりしていないことを確信できます。
  2. async_fuzzing:ブランチ上で実行し、開発サイクルやMRをブロックすることなくコードの深いバグを検出します。

covfuzz-ci.yml元の同期的な例と同じです。

FIPS対応バイナリ

GitLab 15.0から、カバレッジファジングのバイナリはLinux x86上でgolang-fips 、暗号バックエンドとしてOpenSSLを使ってコンパイルされています。詳細はGitLab with GoのFIPS対応をご覧ください。

オフライン環境

オフライン環境でカバレッジファジングを使用する場合:

  1. gitlab-cov-fuzz を、オフラインの GitLab インスタンスがアクセスできる非公開リポジトリにクローンします。

  2. ファジングのステップごとに、COVFUZZ_URL_PREFIX${NEW_URL_GITLAB_COV_FUZ}/-/raw に設定します。NEW_URL_GITLAB_COV_FUZ は、最初のステップで設定した非公開gitlab-cov-fuzz クローンの URL です。

脆弱性との対話

脆弱性が見つかった後、その脆弱性にアドレスすることができます。マージリクエストウィジェットには脆弱性が一覧表示され、ファジングのアーティファクトをダウンロードするためのボタンがコンテナされています。検出された脆弱性の一つを選択すると、その詳細を見ることができます。

Coverage Fuzzing Security Report

また、グループ、プロジェクト、パイプラインのすべてのセキュリティ脆弱性の概要を表示するセキュリティダッシュボードからも、脆弱性を見ることができます。

脆弱性を選択すると、脆弱性に関する追加情報を提供するモーダルが開きます:

  • ステータス:脆弱性のステータス。他のタイプの脆弱性と同様に、カバレッジ・ファジングの脆弱性は、「検出」、「確認」、「却下」、「解決」のいずれかになります。
  • プロジェクト:脆弱性が存在するプロジェクト。
  • クラッシュの種類:コードのクラッシュや脆弱性のタイプ。これは通常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_REGISTRYtrueに設定されている場合、アップロードされたコーパスファイルがcorpusという名前のフォルダに解凍されることを確認してください。

エラー400 Bad request - Duplicate package is not allowed

COVFUZZ_USE_REGISTRYtrueに設定してファジングジョブを実行したときにこのエラーメッセージが表示された場合は、重複が許可されていることを確認してください。詳細については、Genericパッケージの重複を参照してください。