模糊测试 LLVM 库和工具

简介

LLVM 代码树包含许多针对各种组件的模糊测试器。这些模糊测试器构建在 LibFuzzer 之上。要构建和运行这些模糊测试器,请参阅 配置 LLVM 以构建模糊测试器

可用的模糊测试器

clang-fuzzer

一个 通用模糊测试器,尝试将文本输入编译为 C++ 代码。此模糊测试器报告的一些错误位于 bugzilla 上OSS Fuzz 的跟踪器上

clang-proto-fuzzer

一个 基于 libprotobuf-mutator 的模糊测试器,它编译从描述 C++ 语言子集的 protobuf 类生成的有效 C++ 程序。

此模糊测试器在 ignore_remaining_args=1 之后接受 clang 命令行选项。例如,以下命令将使用更高的优化级别模糊测试 clang

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

clang-format-fuzzer

一个 通用模糊测试器,它在 C++ 文本片段上运行 clang-format。此模糊测试器报告的一些错误位于 bugzilla 上OSS 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,OSS Fuzz 在处理必需参数时存在问题。为此,您可以将 llvm-isel-fuzzer 复制或移动到 llvm-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。需要 mtriplepasses 参数。传递以适合新传递管理器的格式指定。您可以在 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 标志必须使用两个连字符。例如,要模糊测试 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-fuzzerllvm-mc-disassemble-fuzzer

使用 libprotobuf-mutator 进行结构化模糊测试

我们可以使用 libprotobuf-mutator 来执行结构化模糊测试并对程序的更深层进行压力测试。这是通过定义一个 protobuf 类来实现的,该类将任意数据转换为结构化有趣的输入。具体来说,我们使用它来处理 C++ 语言的一个子集并执行产生有效 C++ 程序的变异,以便练习 clang 中比解析错误处理更有趣的部件。

要构建这种类型的模糊测试器,您需要安装 protobuf 及其依赖项,并且您需要在使用 CMake 配置构建时指定一些额外的标志。例如,可以通过将 -DCLANG_ENABLE_PROTO_FUZZER=ON 添加到 配置 LLVM 以构建模糊测试器 中描述的标志中来启用 clang-proto-fuzzer

目前唯一使用 libprotobuf-mutator 的树内模糊测试器是 clang-proto-fuzzer

LLVM IR 的结构化模糊测试

我们还对以 LLVM IR 作为输入的模糊测试器使用更直接的结构化模糊测试形式。这是通过 FuzzMutate 库实现的,该库曾在 2017 年的 EuroLLVM 上讨论过

FuzzMutate 库用于在 llvm-isel-fuzzer 中结构化模糊测试后端。

构建和运行

配置 LLVM 以构建模糊测试器

只要您在构建 LLVM 时启用了 sanitizer 覆盖率,fuzzer 就会默认构建并链接到 libFuzzer。您通常还会启用至少一个 sanitizer 以更快地查找错误。构建 fuzzer 最常见的方法是在 CMake 调用中添加以下两个标志:-DLLVM_USE_SANITIZER=Address -DLLVM_USE_SANITIZE_COVERAGE=On

注意

如果您在使用 sanitizer 构建时在 LLVM 树中检出了compiler-rt,则需要指定-DLLVM_BUILD_RUNTIME=Off 以避免使用启用的 sanitizer 构建 sanitizer 本身。

注意

如果您使用 BFD ld 构建,可能会遇到问题,BFD ld 是许多 Unix 系统上的默认链接器。这些问题正在 https://llvm.net.cn/PR34636 中跟踪。

持续运行和查找错误

过去有一个公共的 buildbot 持续运行 LLVM fuzzer,虽然这确实发现了问题,但它没有一个很好的方法以可操作的方式报告问题。因此,我们正转向更多地使用 OSS Fuzz

您可以浏览 LLVM 项目问题列表,以查看由 LLVM 在 OSS Fuzz 上 发现的错误。这些错误也会发送到 llvm-bugs 邮件列表

编写 Fuzzers 的实用程序

LLVM 提供了一些用于编写 fuzzer 的实用程序。

一些用于处理命令行界面的帮助程序位于 include/llvm/FuzzMutate/FuzzerCLI.h 中,包括以一致的方式解析命令行选项以及实现独立主函数的功能,以便在未针对 libFuzzer 构建时可以构建和测试您的 fuzzer。

还有一些用于处理 fuzzer 的 CMake 配置,您应该使用 add_llvm_fuzzer 来设置 fuzzer 目标。此函数的工作方式类似于 add_llvm_tool 等函数,但它们会在适当的时候负责链接到 LibFuzzer,并且可以传递 DUMMY_MAIN 参数以启用独立测试。