libFuzzer によるファジング

ファジングは、プログラムへの入力として潜在的に無効なデータ、予期しないデータ、またはランダムなデータを提供するだけの手法ですが、大規模なソフトウェア システムのバグを見つけるには非常に効果的であり、ソフトウェア開発ライフサイクルの重要な構成要素になっています。

Android のビルドシステムは、LLVM コンパイラ インフラストラクチャ プロジェクトからの libFuzzer を組み込むことで、ファジングをサポートします。LibFuzzer は、テスト対象の関数にリンクされ、ファジング セッション中に発生するすべての入力選択、変換、クラッシュ レポートを処理します。LLVM のサニタイザーは、メモリ破損の検出とコード カバレッジ指標に援用されます。

この記事では、Android での libFuzzer の使用方法とインストゥルメント化ビルドの実行方法を紹介します。また、ファザーの作成、実行、カスタマイズの手順についても説明します。

設定とビルド

デバイス上で正常なイメージが実行されていることを確認するには、ファクトリー イメージをダウンロードしてフラッシュします。または、AOSP ソースコードをダウンロードして、以下のセットアップとビルドの例に従うこともできます。

設定例

この例では、ターゲット デバイスが Pixel(taimen)であり、すでに USB デバッグ(aosp_taimen-userdebug)用に準備されていることを前提としています。他の Pixel バイナリは、ドライバ バイナリからダウンロードできます。

mkdir ~/bin
export PATH=~/bin:$PATH
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
repo init -u https://android.googlesource.com/platform/manifest -b master
repo sync -c -j8
wget https://dl.google.com/dl/android/aosp/google_devices-taimen-qq1a.191205.008-f4537f93.tgz
tar xvf google_devices-taimen-qq1a.191205.008-f4537f93.tgz
./extract-google_devices-taimen.sh
wget https://dl.google.com/dl/android/aosp/qcom-taimen-qq1a.191205.008-760afa6e.tgz
tar xvf qcom-taimen-qq1a.191205.008-760afa6e.tgz
./extract-qcom-taimen.sh
. build/envsetup.sh
lunch aosp_taimen-userdebug

ビルド例

ファズ ターゲットを実行する最初のステップは、最新のシステム イメージを取得することです。Android の最新版の開発をご利用の際は、

  1. 次のコマンドを実行して、最初のビルドを実行します。
    m
  2. デバイスにフラッシュするには、適切なキーの組み合わせを使用して、デバイスを fastboot モードで起動します。
  3. 次のコマンドにより、ブートローダーのロックを解除して、新しくコンパイルされたイメージをフラッシュします
    fastboot oem unlock
    fastboot flashall
    

これで、ターゲット デバイスで libFuzzer ファジングを行う準備が整いました。

ファザーを作成する

Android で libFuzzer を使用してエンドツーエンドのファザーを作成する方法を説明するため、テストケースとして次の脆弱なコードを使用します。これにより、ファザーをテストして、すべてが正常に機能していることを確認し、クラッシュ データがどのように表示されるかを示します。

テスト関数は次のとおりです。

#include <stdint.h>
#include <stddef.h>
bool FuzzMe(const char *Data, size_t DataSize) {
    return DataSize >= 3  &&
           Data[0] == 'F' &&
           Data[1] == 'U' &&
           Data[2] == 'Z' &&
           Data[3] == 'Z';  // ← Out of bounds access
}

このテスト用ファザーをビルドして実行するには:

  1. ファズ ターゲットは、ビルド ファイルとファジング ターゲット ソースコードの 2 つのファイルで構成されます。 拡張するライブラリの横の場所にファイルを作成します。ファザーには、ファザーが何をするのかを説明する名前を付けます。
  2. libFuzzer を使用してファズ ターゲットを作成します。ファズ ターゲットは、指定されたサイズのデータの blob を受け取って、ファジング対象の関数に渡す関数です。脆弱なテスト関数の基本的なファザーは以下のようになります。
    #include <stddef.h>
    #include <stdint.h>
    
    extern "C" int LLVMFuzzerTestOneInput(const char *data, size_t size) {
      // ...
      // Use the data to call the library you are fuzzing.
      // ...
      return FuzzMe(data, size);
    }
    
  3. Android のビルドシステムに、ファザー バイナリの作成を指示します。 ファザーをビルドするには、次のコードを Android.bp ファイルに追加します。
    cc_fuzz {
      name: "fuzz_me_fuzzer",
      srcs: [
        "fuzz_me_fuzzer.cpp",
      ],
      // If the fuzzer has a dependent library, uncomment the following section and
      // include it.
      // static_libs: [
      //   "libfoo", // Dependent library
      // ],
      //
      // The advanced features below allow you to package your corpus and
      // dictionary files during building. You can find more information about
      // these features at:
      //  - Corpus: https://llvm.org/docs/LibFuzzer.html#corpus
      //  - Dictionaries: https://llvm.org/docs/LibFuzzer.html#dictionaries
      // These features are not required for fuzzing, but are highly recommended
      // to gain extra coverage.
      // To include a corpus folder, uncomment the following line.
      // corpus: ["corpus/*"],
      // To include a dictionary, uncomment the following line.
      // dictionary: "fuzz_me_fuzzer.dict",
    }
    
  4. ターゲット(デバイス)で実行するためのファザーを作成するには:
    SANITIZE_TARGET=hwaddress m fuzz_me_fuzzer
    
  5. ホスト上でファザーを実行するには:
    SANITIZE_HOST=address m fuzz_me_fuzzer
    

便宜上、ファジー ターゲットへのパスとバイナリ名(先ほど作成したビルドファイルから)を含むシェル変数を定義します。

export FUZZER_NAME=your_fuzz_target

以上のステップを完了すると、ファザーがビルドされます。ファザーのデフォルトの場所は、(この例の Pixel ビルドでは) です。

  • $ANDROID_PRODUCT_OUT/data/fuzz/$TARGET_ARCH/$FUZZER_NAME/$FUZZER_NAME になります。
  • ホスト $ANDROID_HOST_OUT/fuzz/$TARGET_ARCH/$FUZZER_NAME/$FUZZER_NAME
  • ホストでファザーを実行する

  • Android.bp ビルドファイルに追加します。
    host_supported: true,
    これは、融合するライブラリがホストでサポートされている場合にのみ適用できます。
  • ビルドされたファザー バイナリを実行するだけで、ファザーを fuzzer で実行します。
    $ANDROID_HOST_OUT/fuzz/x86_64/$FUZZER_NAME/$FUZZER_NAME
  • デバイスで Fuzzer を実行する

    adb を使ってデバイスにこのコピーをコピーします。

    1. これらのファイルをデバイスのディレクトリにアップロードするには、次のコマンドを実行します。
      adb root
      adb sync data
       
    2. 次のコマンドを使用して、デバイスでテストファザーを実行します。
      adb shell /data/fuzz/$(get_build_var TARGET_ARCH)/$FUZZER_NAME/$FUZZER_NAME \
        /data/fuzz/$(get_build_var TARGET_ARCH)/$FUZZER_NAME/corpus

    これにより、次のような出力が得られます。

    INFO: Seed: 913963180
    INFO: Loaded 2 modules   (16039 inline 8-bit counters): 16033 [0x7041769b88, 0x704176da29), 6 [0x60e00f4df0, 0x60e00f4df6),
    INFO: Loaded 2 PC tables (16039 PCs): 16033 [0x704176da30,0x70417ac440), 6 [0x60e00f4df8,0x60e00f4e58),
    INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
    INFO: A corpus is not provided, starting from an empty corpus
    #2	INITED cov: 5 ft: 5 corp: 1/1b exec/s: 0 rss: 24Mb
    #10	NEW    cov: 6 ft: 6 corp: 2/4b lim: 4 exec/s: 0 rss: 24Mb L: 3/3 MS: 3 CopyPart-ChangeByte-InsertByte-
    #712	NEW    cov: 7 ft: 7 corp: 3/9b lim: 8 exec/s: 0 rss: 24Mb L: 5/5 MS: 2 InsertByte-InsertByte-
    #744	REDUCE cov: 7 ft: 7 corp: 3/7b lim: 8 exec/s: 0 rss: 25Mb L: 3/3 MS: 2 ShuffleBytes-EraseBytes-
    #990	REDUCE cov: 8 ft: 8 corp: 4/10b lim: 8 exec/s: 0 rss: 25Mb L: 3/3 MS: 1 ChangeByte-
    ==18631==ERROR: HWAddressSanitizer: tag-mismatch on address 0x0041e00b4183 at pc 0x0060e00c5144
    READ of size 1 at 0x0041e00b4183 tags: f8/03 (ptr/mem) in thread T0
        #0 0x60e00c5140  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf140)
        #1 0x60e00ca130  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x14130)
        #2 0x60e00c9b8c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x13b8c)
        #3 0x60e00cb188  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15188)
        #4 0x60e00cbdec  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15dec)
        #5 0x60e00d8fbc  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x22fbc)
        #6 0x60e00f0a98  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x3aa98)
        #7 0x7041b75d34  (/data/fuzz/arm64/lib/libc.so+0xa9d34)
    
    [0x0041e00b4180,0x0041e00b41a0) is a small allocated heap chunk; size: 32 offset: 3
    0x0041e00b4183 is located 0 bytes to the right of 3-byte region [0x0041e00b4180,0x0041e00b4183)
    allocated here:
        #0 0x70418392bc  (/data/fuzz/arm64/lib/libclang_rt.hwasan-aarch64-android.so+0x212bc)
        #1 0x60e00ca040  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x14040)
        #2 0x60e00c9b8c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x13b8c)
        #3 0x60e00cb188  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15188)
        #4 0x60e00cbdec  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x15dec)
        #5 0x60e00d8fbc  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x22fbc)
        #6 0x60e00f0a98  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x3aa98)
        #7 0x7041b75d34  (/data/fuzz/arm64/lib/libc.so+0xa9d34)
        #8 0x60e00c504c  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf04c)
        #9 0x70431aa9c4  (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0x519c4)
    
    Thread: T1 0x006700006000 stack: [0x007040c55000,0x007040d4ecc0) sz: 1023168 tls: [0x000000000000,0x000000000000)
    Thread: T0 0x006700002000 stack: [0x007fe51f3000,0x007fe59f3000) sz: 8388608 tls: [0x000000000000,0x000000000000)
    Memory tags around the buggy address (one tag corresponds to 16 bytes):
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       08  00  cf  08  dc  08  cd  08  b9  08  1a  1a  0b  00  04  3f
    => 27  00  08  00  bd  bd  2d  07 [03] 73  66  66  27  27  20  f6 <=
       5b  5b  87  87  03  00  01  00  4f  04  24  24  03  39  2c  2c
       05  00  04  00  be  be  85  85  04  00  4a  4a  05  05  5f  5f
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
       00  00  00  00  00  00  00  00  00  00  00  00  00  00  00  00
    Tags for short granules around the buggy address (one tag corresponds to 16 bytes):
       04  ..  ..  cf  ..  dc  ..  cd  ..  b9  ..  ..  3f  ..  57  ..
    => ..  ..  21  ..  ..  ..  ..  2d [f8] ..  ..  ..  ..  ..  ..  .. <=
       ..  ..  ..  ..  9c  ..  e2  ..  ..  4f  ..  ..  99  ..  ..  ..
    See https://clang.llvm.org/docs/HardwareAssistedAddressSanitizerDesign.html#short-granules for a description of short granule tags
    Registers where the failure occurred (pc 0x0060e00c5144):
        x0  f8000041e00b4183  x1  000000000000005a  x2  0000000000000006  x3  000000704176d9c0
        x4  00000060e00f4df6  x5  0000000000000004  x6  0000000000000046  x7  000000000000005a
        x8  00000060e00f4df0  x9  0000006800000000  x10 0000000000000001  x11 00000060e0126a00
        x12 0000000000000001  x13 0000000000000231  x14 0000000000000000  x15 000e81434c909ede
        x16 0000007041838b14  x17 0000000000000003  x18 0000007042b80000  x19 f8000041e00b4180
        x20 0000006800000000  x21 000000000000005a  x22 24000056e00b4000  x23 00000060e00f5200
        x24 00000060e0128c88  x25 00000060e0128c20  x26 00000060e0128000  x27 00000060e0128000
        x28 0000007fe59f16e0  x29 0000007fe59f1400  x30 00000060e00c5144
    SUMMARY: HWAddressSanitizer: tag-mismatch (/data/fuzz/arm64/example_fuzzer/example_fuzzer+0xf140)
    MS: 1 ChangeByte-; base unit: e09f9c158989c56012ccd88111b82f778a816eae
    0x46,0x55,0x5a,
    FUZ
    artifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60
    Base64: RlVa
    

    この出力例では、クラッシュの原因は 10 行目の fuzz_me_fuzzer.cpp です。

          Data[3] == 'Z';  // :(
    

    Data の長さが 3 であれば、これは単純な境界外の読み取りです。

    ファザーを実行すると、多くの場合出力がクラッシュし、不適切な入力がコーパスに保存されて ID が与えられます。この出力例では、ID は crash-0eb8e4ed029b774d80f2b66408203801cb982a60 です。

    デバイスでファジング中にクラッシュ情報を取得するには、次のコマンドを発行してクラッシュ ID を指定します。

    adb pull /data/fuzz/arm64/fuzz_me_fuzzer/corpus/CRASH_ID
    テストケースを適切なディレクトリに保存するには、corpus フォルダ(上記の例など)を使用するか、artifact_prefix 引数を使用します(例: `-artifact_prefix=/data/fuzz/where/my/crashes/go\ など)。

    ホストでファザーを使用する場合、ファザーが実行されるローカル フォルダのクラッシュ フォルダにクラッシュ情報が表示されます。

    libFuzzer の詳細については、アップストリーム ドキュメントをご覧ください。