LLVMライブラリとツールのファジング

はじめに

LLVMツリーには、さまざまなコンポーネント用の多くのファザーが含まれています。これらはLibFuzzerの上に構築されています。これらのファザーをビルドおよび実行するには、ファザーをビルドするためのLLVMの構成を参照してください。

利用可能なファザー

clang-fuzzer

テキスト入力をC++コードとしてコンパイルしようとする汎用ファザー。このファザーが報告したバグの一部は、bugzillaOSS Fuzzのトラッカーにあります。

clang-proto-fuzzer

C++言語のサブセットを記述するprotobufクラスから生成された有効なC++プログラムをコンパイルする、libprotobuf-mutatorベースのファザー

このファザーは、ignore_remaining_args=1の後にclangコマンドラインオプションを受け入れます。たとえば、次のコマンドは、より高い最適化レベルでclangをファジングします

% bin/clang-proto-fuzzer <corpus-dir> -ignore_remaining_args=1 -O3

clang-format-fuzzer

C++テキストフラグメントでclang-formatを実行する汎用ファザー。このファザーが報告したバグの一部は、bugzillaOSS Fuzzのトラッカーにあります。

llvm-as-fuzzer

テキストをLLVMアセンブリとして解析しようとする汎用ファザー。このファザーが報告したバグの一部はbugzillaにあります。

llvm-dwarfdump-fuzzer

入力をオブジェクトファイルとして解釈し、それらに対してllvm-dwarfdumpを実行する汎用ファザー。このファザーが報告したバグの一部は、OSS Fuzzのトラッカーにあります

llvm-demangle-fuzzer

さまざまなLLVMツールで使用されているItaniumデマングラー用の汎用ファザー。__cxa_demangleを徹底的にファジングしたので、同じ関数のLLVM実装をファジングしてみましょう!

llvm-isel-fuzzer

命令選択のバグを見つけることを目的とした構造化LLVM IRファザー

このファザーは、ignore_remaining_args=1の後にフラグを受け入れます。フラグはllcのフラグと一致し、トリプルが必要です。たとえば、次のコマンドは、グローバル命令選択を使用してAArch64をファジングします

% bin/llvm-isel-fuzzer <corpus-dir> -ignore_remaining_args=1 -mtriple aarch64 -global-isel -O0

一部のフラグは、必須引数に問題があるOSS Fuzzをサポートするために、バイナリ名自体で指定することもできます。これを行うには、llvm-isel-fuzzerllvm-isel-fuzzer--x-y-zにコピーまたは移動し、オプションをバイナリ名から「--」を使用して分離します。有効なオプションは、アーキテクチャ名(aarch64x86_64)、最適化レベル(O0O2)、またはグローバル命令選択を有効にするためのgiselなどの特定のキーワードです。このモードでは、同じ例を次のように実行できます

% bin/llvm-isel-fuzzer--aarch64-O0-gisel <corpus-dir>

llvm-opt-fuzzer

最適化パスのバグを見つけることを目的とした構造化LLVM IRファザー

最適化パイプラインを受け取り、各ファザー入力に対して実行します。

このファザーのインターフェースは、llvm-isel-fuzzerをほぼ直接ミラーリングしています。mtriple引数とpasses引数の両方が必要です。パスは、新しいパスマネージャーに適した形式で指定されます。この形式に関するドキュメントは、PassBuilder::parsePassPipelineのdoxygenにあります。

% bin/llvm-opt-fuzzer <corpus-dir> -ignore_remaining_args=1 -mtriple x86_64 -passes instcombine

llvm-isel-fuzzerの引数と同様に、一部の事前定義された構成をバイナリファイル名に直接埋め込むことができます

% bin/llvm-opt-fuzzer--x86_64-instcombine <corpus-dir>

llvm-mc-assemble-fuzzer

入力をターゲット固有のアセンブリとして扱うことにより、MCレイヤーのアセンブラーをファジングする汎用ファザー

このファザーには、libFuzzerのすべての機能と完全には互換性のない異常なコマンドラインインターフェースがあることに注意してください。ファザーの引数は--fuzzer-argsの後に渡す必要があり、llcフラグは2つのダッシュを使用する必要があります。たとえば、AArch64アセンブラーをファジングするには、次のコマンドを使用できます

llvm-mc-fuzzer --triple=aarch64-linux-gnu --fuzzer-args -max_len=4

このスキームは将来変更される可能性があります。

llvm-mc-disassemble-fuzzer

入力をアセンブルされたバイナリデータとして扱うことにより、MCレイヤーのディスアセンブラーをファジングする汎用ファザー

このファザーには、libFuzzerのすべての機能と完全には互換性のない異常なコマンドラインインターフェースがあることに注意してください。詳細については、上記のllvm-mc-assemble-fuzzerに関するメモを参照してください。

lldb-target-fuzzer

入力をオブジェクトファイルとして解釈し、それらを使用してlldbにターゲットを作成する汎用ファザー

ミューテーターと入力ジェネレーター

ファズターゲットの入力は、コーパスのランダムな変異によって生成されます。LLVMのファザーで必要になる可能性のある変異の種類には、いくつかのオプションがあります。

ジェネリックランダムファジング

入力変異の最も基本的な形式は、LibFuzzerの組み込みミューテーターを使用することです。これらは単に入力コーパスをビットの袋として扱い、ランダムな変異を行います。このタイプのファザーは、プログラムの表面レイヤーにストレスを与えるのに適しており、レクサー、パーサー、バイナリプロトコルなどのテストに適しています。

このタイプのミューテーターを使用するインツリーファザーの一部は、clang-fuzzerclang-format-fuzzerllvm-as-fuzzerllvm-dwarfdump-fuzzerllvm-mc-assemble-fuzzer、およびllvm-mc-disassemble-fuzzerです。

libprotobuf-mutatorを使用した構造化ファジング

libprotobuf-mutatorを使用して、構造化ファジングを実行し、プログラムのより深いレイヤーにストレスを与えることができます。これは、任意のデータを構造的に興味深い入力に変換するprotobufクラスを定義することによって機能します。具体的には、これを使用してC++言語のサブセットを操作し、パーサーエラー処理よりも興味深いclangの部分を実行するために有効なC++プログラムを生成する変異を実行します。

この種のファザーを構築するには、protobufとその依存関係がインストールされている必要があります。また、CMakeでビルドを構成する際に、追加のフラグを指定する必要があります。たとえば、clang-proto-fuzzerは、ファザーをビルドするためのLLVMの構成で説明されているフラグに-DCLANG_ENABLE_PROTO_FUZZER=ONを追加することで有効にできます。

現在、libprotobuf-mutatorを使用しているインツリーファザーはclang-proto-fuzzerのみです。

LLVM IRの構造化ファジング

また、LLVM IRを入力として受け取るファザーには、より直接的な形式の構造化ファジングを使用します。これは、FuzzMutateライブラリを通じて実現されます。このライブラリは、EuroLLVM 2017で議論されました

FuzzMutateライブラリは、llvm-isel-fuzzerでバックエンドを構造的にファジングするために使用されます。

ビルドと実行

ファザーをビルドするためのLLVMの構成

サニタイザーカバレッジを有効にしてLLVMをビルドすると、デフォルトでファザーがビルドされ、libFuzzerにリンクされます。通常、バグをより迅速に見つけるために、少なくとも1つのサニタイザーも有効にします。ファザーをビルドする最も一般的な方法は、CMakeの呼び出しに次の2つのフラグを追加することです。-DLLVM_USE_SANITIZER=Address -DLLVM_USE_SANITIZE_COVERAGE=On

注記

サニタイザーを使用してビルドするときに、LLVMツリーにcompiler-rtをチェックアウトしている場合は、サニタイザー自体をサニタイザーを有効にしてビルドすることを避けるために、-DLLVM_BUILD_RUNTIME=Offを指定する必要があります。

注記

多くのUnixシステムでデフォルトのリンカーであるBFD ldを使用してビルドすると、問題が発生する可能性があります。これらの問題は、https://llvm.dokyumento.jp/PR34636で追跡されています。

継続的な実行とバグの発見

以前は、LLVMファザーを継続的に実行するパブリックビルドボットがありましたが、これは問題を検出しましたが、実用的な方法で問題を報告するための優れた方法がありませんでした。このため、代わりにOSS Fuzzを使用する方向に移行しています。

LLVMプロジェクトの問題リストで、OSS FuzzのLLVMによって発見されたバグを参照できます。これらは、llvm-bugsメーリングリストにもメールで送信されます。

ファザーを記述するためのユーティリティ

LLVMでファザーを記述するために利用できるユーティリティがいくつかあります。

コマンドラインインターフェースを処理するためのいくつかのヘルパーは、include/llvm/FuzzMutate/FuzzerCLI.hで利用できます。これには、コマンドラインオプションを一貫した方法で解析する関数や、libFuzzerに対してビルドされていないときにファザーをビルドおよびテストできるようにスタンドアロンのメイン関数を実装する関数が含まれます。

ファザーのCMake構成の処理もいくつかあり、ファザーターゲットを設定するにはadd_llvm_fuzzerを使用する必要があります。この関数は、add_llvm_toolなどの関数と同様に動作しますが、適切な場合はLibFuzzerへのリンクを処理し、スタンドアロンのテストを有効にするためにDUMMY_MAIN引数を渡すことができます。