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,后者在处理必需参数时遇到问题。 为此,您可以将 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-fuzzermtriplepasses 参数都是必需的。 通行证以适合新通行证管理器的格式指定。 您可以在 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 库实现的,该库在 EuroLLVM 2017 上进行了讨论

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

构建和运行

配置 LLVM 以构建模糊测试器

默认情况下,只要您使用启用的 sanitizer coverage 构建 LLVM,就会构建模糊测试器并将其链接到 libFuzzer。 您通常还需要启用至少一个 sanitizer 以更快地查找错误。 构建模糊测试器最常见的方法是将以下两个标志添加到您的 CMake 调用中:-DLLVM_USE_SANITIZER=Address -DLLVM_USE_SANITIZE_COVERAGE=On

注意

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

注意

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

持续运行和查找错误

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

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

用于编写模糊测试器的实用工具

有一些实用工具可用于在 LLVM 中编写模糊测试器。

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

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