
UTopia は、ユニットテストからファズドライバを自動生成するためのツールです。
UTopia を利用すると、開発者はファザーの書き方に関する特別な知識がなくてもファズテストを実行できます。ファジングに精通した開発者でも、ファズドライバを自動生成することで大幅に時間を節約できます。
UTopia は、GoogleTest、Boost.Test、Tizen TCT を使用したユニットテストを持つ C/C++ ライブラリをサポートしています。
UTopia が見つけたバグを確認するには、Trophy ページを参照してください。また、UTopia ベースのファザーも確認できます。
簡単にセットアップできるよう、UTopia を実行するための docker image を提供しています。 docker コンテナ内で UTopia をビルドし、ファザーの生成・実行ができます。
以下のコマンドで docker image をビルドします。
docker buildx build -f docker/Dockerfile -t utopia . #llvm-10
docker buildx build --build-arg LLVM_VERSION=12 -f docker/Dockerfile -t utopia . #llvm-12
UTopia は LLVM バージョン 10 で最もよく動作するように設計されています。また、LLVM バージョン 12 でユニットテストに合格することが確認されています。そのため、LLVM 10 の使用を推奨しますが、希望があれば LLVM バージョン 12 も試せます。
UTopia は LLVM、Protobuf、GoogleTest に依存しています。依存関係を手動でインストールすることもできますが、提供されている docker image を使用することをお勧めします。
リポジトリをクローンした後、すべてのサブモジュールを初期化して更新します:
git submodule update --init --recursive
UTopia をビルドするには、以下の cmake プロセスに従ってください。
cd $UTOPIA_HOME_DIR
cmake -B build -S .
cmake --build build -j$(nproc)
一部の選択されたプロジェクトでは、追加の手間なくツールを実行できるヘルパースクリプトを使用できます。helper/README.md を参照してください。その他のプロジェクトについては、以下のマニュアルを確認してください。
Target Analyzer は対象ライブラリのコードを解析し、結果を json ファイルとして生成します。必須のコマンドラインオプションは以下のとおりです。
target_analyzer --db ${builddb_path} --extern ${extern_path} --public ${api_json_path} --out ${output_path}
Build db は、対象ライブラリコードの AST および IR ファイルのパスを含む json ファイルです。その形式は以下のようになります。
{
"bc": "/root/fuzz-test-generation/exp/sample/output/bc/libcommon.a.bc",
"ast": [
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast1.o.ast",
"/root/fuzz-test-generation/exp/sample/libcommon.a_ast/codec/common/src/ast2.o.ast"
],
"project_dir": "/root/fuzz-test-generation/exp/sample"
}
特定のライブラリの LLVM bitcode ファイルパスは、"bc" キーワードを使用して指定する必要があります。現在受け付けている bitcode ファイルは1つだけなので、複数の bitcode ファイルを1つの bitcode ファイルにリンクするには llvm-link を使用してください。
特定のライブラリの AST ファイルパスは、"ast" キーワードを使用して指定する必要があります。指定した bitcode ファイルと ast ファイルは、特定のライブラリの同じソースコードから生成されることに注意してください。
Target Analyzer は、正確な結果を得るために、他のライブラリの target analyzer レポートを受け付けます。このパスは、他のレポートが保存されているディレクトリパスである必要があります。つまり、複数のライブラリのレポートを許可します。
解析対象の API 関数名です。以下のような形式の json ファイルである必要があります。
{
"libcommon.a": [
"API1",
"API2",
"API3"
]
}
以下のコマンドを使用して、特定のライブラリから API リストを取得できます。
nm --no-demangle --defined-only -g ${librarypath} | awk '$2=="T" {k=""; for(i=3;i<=NF;i++) k=k $i""; print k}'
Direction プロパティは、target analyzer が利用する重要なパラメータです。これは、関数内でパラメータが読み取り(Dir_In)、書き込み(Dir_Out)、または読み取りと書き込みの両方(Dir_In | Dir_Out)に使用されるかどうかを示します。target analyzer は、このプロパティを以下の要素からなる列挙型で定義しています:
enum Dir {
Dir_NoOp = 0x000, // No operation
Dir_In = 0x100, // Input direction
Dir_Out = 0x010, // Output direction
Dir_Unidentified = 0x001 // Unidentified direction
};
たとえば、以下の JSON ファイルの抜粋では:
{
"Direction": {
"BF_crypt(0)": 256,
"BF_crypt(1)": 272,
"BF_crypt(2)": 272,
"BF_decode(1)": 272
}
}
BF_crypt 関数の最初のパラメータの方向が 256 (0x100) に設定されていることを示しており、In 方向、つまり入力に使用されることを意味します。BF_crypt 関数の2番目のパラメータの方向が 272 (0x110) であることを示しており、In と Out の両方の方向を持つ、つまり入力と出力の両方に使用されることを意味します。この表記により、関数内の各パラメータが入力・出力・またはその両方のいずれに使用されるかを理解でき、関数が実行するデータフローと操作について明確な洞察が得られます。
UT Analyzer は、対象ライブラリのユニットテストコードを解析し、結果を json ファイルとして生成します。必須のコマンドラインオプションは以下のとおりです。
ut_analyzer --entry ${entry_path} --extern ${extern_path} --ut ${ut_type} --name ${lib_name} --public ${api_json_path} --out ${output_path}
ほとんどのオプションは target_analyzer と同じです。entry_path には、ライブラリではなくユニットテスト実行可能ファイルの AST/IR ファイルを指定する必要があることに注意してください。
ターゲットプロジェクトが使用するフレームワークです。tct、gtest、boost のいずれかです。
fuzz_generator は、target_anlayzer と ut_analyzer のレポートファイルを使用してファズドライバを生成します。必須のコマンドラインオプションは以下のとおりです。
fuzz_generator --src ${src_path} --target ${target_analyzer_report_path} --ut ${ut_analyzer_report_path} --public ${api_json_path} --out ${output_dir}
src_path は、ユニットテストのソースコードが保存されているディレクトリパスです。fuzz_generator はこのディレクトリをコピーし、コピーしたファイルを変更してファズドライバを生成します。
その他のオプションは target_analyzer のオプションと同じです。
このセクションでは、生成されたファズドライバの機能と実装の詳細について説明します。これは、ファズテストがターゲットソースコードとどのように統合されるかを理解するために重要です。
fuzz_entry.cc(自動生成ファイル)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
この関数は、生成されたファズドライバのエントリポイントとして機能します。ファザーによって生成された値を取得し、それらをライブラリ関数の呼び出しに使用される変数に代入します。最後に、enterAutofuzz(); を呼び出してファズテストプロセスを進めます。
ターゲットテストケースを定義するソースコード
#ifdef __cplusplus
extern "C" {
#endif
void enterAutofuzz() {
class AutofuzzTest : public ::Parser_TestArray_Test {
public:
void runTest() {
try {
SetUpTestCase();
} catch (std::exception &E) {}
try {
SetUp();
} catch (std::exception &E) {}
try {
TestBody();
} catch (std::exception &E) {}
try {
TearDown();
} catch (std::exception &E) {}
try {
TearDownTestCase();
} catch (std::exception &E) {}
}
};
AutofuzzTest Fuzzer;
Fuzzer.runTest();
}
#ifdef __cplusplus
}
#endif
ターゲットテストケースを定義するソースコードの最後の部分で、UTopia は enterAutofuzz 関数を注入します。この関数内では、::Parser_TestArray_Test を継承する AutofuzzTest クラスが宣言されます。この親クラスは GoogleTest フレームワークによって定義され、対象となるテストケースに固有のものです。以下に示します:
TEST(Parser, TestArray)
{
...
}
runTest() メソッドは、GoogleTest に依存せずにテストケースを実行し、GoogleTest が通常各テストケースで呼び出す5つの関数を呼び出します。この方法により、GoogleTest フレームワークに頼らずにテストを直接実行できます。
ファズドライバは、fuzz_generator にコマンドラインオプションとして渡された ${output_dir} に生成されます。ユニットテスト実行可能ファイルと同じコンパイラコマンドを使用してビルドできます。なお、fuzz_generator が生成する fuzz_entry.cc と FuzzArgsProto.pb.cc ファイルを含める必要があります。これらのファイルは ${output_dir} にあります。
python3 -m helper.make {library name}
python3 -m helper.build {library name}
'UTopia' のパイプライン全体のすべての出力は、次の2つのディレクトリにあります:
exp/{library name}/output,result/test/{library name}