UTopia 是一个根据单元测试自动生成 fuzz driver 的工具。
UTopia 让开发者无需具备编写 fuzzer 的特殊知识即可进行模糊测试。即使是熟悉模糊测试的开发者 也能通过自动生成 fuzz driver 节省大量时间。
UTopia 支持带有 GoogleTest、Boost.Test 或 Tizen TCT 单元测试的 C/C++ 库。
要查看由 UTopia 发现的 bug,请访问 战果 页面。你还可以在那里看到一些基于 UTopia 的 fuzzer。
为便于搭建,我们提供了用于运行 UTopia 的 docker 镜像。 你可以在 docker 容器内构建 UTopia 并生成/运行 fuzzer。
使用下面的命令构建 docker 镜像。
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 镜像。
克隆仓库后,初始化并更新所有子模块:
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 是一个 json 文件,包含目标库代码的 AST 和 IR 文件路径。其格式如下所示。
{
"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 文件,因此你可以使用 llvm-link 将多个 bitcode 文件链接到一个 bitcode 文件。
特定库的 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
}
}
这种表示方法有助于理解函数中每个参数的用途,是用于输入、输出还是两者兼有,从而清晰地了解函数所执行的数据流和操作。
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 driver。必需的命令行选项如下。
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 会复制该目录,并通过修改这些复制的文件来生成 fuzz driver。
其他选项与 target_analyzer 相同。
本节介绍所生成的 fuzz driver 的功能和实现细节,这对于理解模糊测试如何与目标源代码集成至关重要。
fuzz_entry.cc(自动生成的文件)
DEFINE_PROTO_FUZZER(const AutoFuzz::FuzzArgsProfile &autofuzz_mutation) {
... /* Values are assigned from autofuzz_mutation */
enterAutofuzz();
}
该函数是所生成的 fuzz driver 的入口点。它获取 fuzzer 生成的值,并将这些值赋给随后用于调用库函数的变量。最后,该函数调用 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 函数。在该函数中声明了 AutofuzzTest 类,它继承自 ::Parser_TestArray_Test。该父类由 GoogleTest 框架定义,并特定于当前处理的测试用例,如下所示:
TEST(Parser, TestArray)
{
...
}
runTest() 方法独立于 GoogleTest 执行测试用例,调用 GoogleTest 通常为每个测试用例调用的五个函数。这种方法允许直接执行测试,而无需依赖 GoogleTest 框架。
fuzz driver 生成在作为命令行选项传递给 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 整个流水线的所有输出:
exp/{library name}/output,result/test/{library name}