ファジングは、プログラムへの入力として潜在的に無効なデータ、予期しないデータ、またはランダムなデータを提供するだけの手法ですが、大規模なソフトウェア システムのバグを見つけるには非常に効果的であり、ソフトウェア開発ライフサイクルの重要な構成要素になっています。
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 main
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 の最新の開発版を使用することが推奨されます。
- 次のコマンドを実行して、最初のビルドを実行します。
m
- デバイスにフラッシュするには、適切なキーの組み合わせを使用して、デバイスを fastboot モードで起動します。
- 次のコマンドにより、ブートローダーのロックを解除して、新しくコンパイルされたイメージをフラッシュします。
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 }
このテスト用ファザーをビルドして実行するには:
- ファズ ターゲットは、ビルド ファイルとファジング ターゲット ソースコードの 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); }
- 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", }
- ターゲット(デバイス)で実行するためのファザーを作成するには、次のコードを追加します。
SANITIZE_TARGET=hwaddress m fuzz_me_fuzzer
- ホスト上でファザーを実行するには、次のコードを追加します。
SANITIZE_HOST=address m fuzz_me_fuzzer
便宜上、ファズ ターゲットへのパスとバイナリ名(先ほど作成したビルドファイルから)を含むシェル変数を定義します。
export FUZZER_NAME=your_fuzz_target
以上のステップを完了すると、ファザーがビルドされます。ファザーのデフォルトの場所は、(この例の Pixel ビルドでは)以下のようになります。
ホストでファザーを実行する
host_supported: true,なお、これはファジングしたいライブラリがホストでサポートされている場合にのみ適用できます。
$ANDROID_HOST_OUT/fuzz/x86_64/$FUZZER_NAME/$FUZZER_NAME
デバイスでファザーを実行する
adb
を使って以下をデバイスにコピーします。
- これらのファイルをデバイスのディレクトリにアップロードするには、次のコマンドを実行します。
adb root
adb sync data
- 次のコマンドを使用して、デバイスでテストファザーを実行します。
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テストケースを適切なディレクトリに保存するには、上記の例のようにコーパス フォルダを使用するか、artifact_prefix 引数(例: `-artifact_prefix=/data/fuzz/where/my/crashes/go`)を使用します。
ホストでファザーを使用する場合、ファザーが実行されるローカル フォルダのクラッシュ フォルダにクラッシュ情報が表示されます。
ライン カバレッジを生成する
ライン カバレッジを利用すると、デベロッパーは、コード内のカバーされていない範囲を特定し、今後のファジングの実行でその範囲をヒットできるように適宜ファザーを更新することができるため、非常に便利です。
- ファザー カバレッジ レポートを生成する手順は次のとおりです。
CLANG_COVERAGE=true NATIVE_COVERAGE_PATHS='*' make ${FUZZER_NAME}
- ファザーとその依存関係をデバイスにプッシュした後、次のように
LLVM_PROFILE_FILE
でファズ ターゲットを実行します。DEVICE_TRACE_PATH=/data/fuzz/$(get_build_var TARGET_ARCH)/${FUZZER_NAME}/data.profraw
adb shell LLVM_PROFILE_FILE=${DEVICE_TRACE_PATH} /data/fuzz/$(get_build_var TARGET_ARCH)/${FUZZER_NAME}/${FUZZER_NAME} -runs=1000
- カバレッジ レポートを作成するには、以下に示すように、まずデバイスから profraw ファイルをプルし、coverage-html という名前のフォルダに html レポートを生成します。
adb pull ${DEVICE_TRACE_PATH} data.profraw
llvm-profdata merge --sparse data.profraw --output data.profdata
llvm-cov show --format=html --instr-profile=data.profdata \ symbols/data/fuzz/$(get_build_var TARGET_ARCH)/${FUZZER_NAME}/${FUZZER_NAME} \ --output-dir=coverage-html --path-equivalence=/proc/self/cwd/,$ANDROID_BUILD_TOP