在 Fuchsia 上使用 libFuzzer 对 FIDL 服务器进行模糊测试
Fuchsia 包含对以 FIDL 单元测试样式编写 FIDL fuzzer 的实验性支持。
快速入门指南
//examples/fuzzers/fidl
中定义了一个模糊测试工具示例。如果您
不熟悉模糊测试工具,请参阅概览。如需在 Fuchsia 上使用 libFuzzer 对 FIDL 服务器进行模糊测试,需要使用会生成模糊测试目标的 GN 目标,并编写一些代码来提供要进行模糊测试的服务器实例。
- 将
fuzzers = {protocol = "fully.qualified.fidl.ProtocolName"}
添加到您的fidl()
GN 目标。 - 根据您使用的语言,执行以下操作:
- 在 C++ 级别(简单模式):使用
//sdk/lib/fidl/hlcpp/fuzzing/include/lib/fidl/cpp/fuzzing/server_provider.h
中的FIDL_FUZZER_DEFINITION()
宏为接口和服务器实现类定义服务器提供程序。这将 自动定义下文所述的 C 符号。请参阅//examples/fuzzers/fidl
获取参考示例。 - 在 C 级(硬模式):
实现可定义以下符号的库:
zx_status_t fuzzer_init()
:实例化服务器实现。zx_status_t fuzzer_connect(zx_handle_t, async_dispatcher_t*)
:将服务器实现绑定到通道句柄。(可选)如果您的服务器 与模糊测试工具客户端相同的线程(请参阅有关线程处理的注意事项)。zx_status_t fuzzer_disconnect(zx_handle_t, async_dispatcher_t*)
:解除服务器实现与通道句柄之间的绑定。zx_status_t fuzzer_clean_up()
:清理服务器实现。 如果其中有任何一项返回ZX_OK
以外的状态,则模糊测试工具将进行清理并停止。
- 在 C++ 级别(简单模式):使用
- 定义
fidl_protocol_fuzzer()
GN 目标。指定:fidl = //path/to:fidl_gn_target
(上述fidl()
目标)。protocol = "fully.qualified.fidl.ProtocolName"
。deps = [... :your_library ...]
(上面提到的用于定义fuzzer_...
符号的文件)。fuzzer
GN 目标所需的任何其他信息, 服务器。
- 在新的或现有的
fuzzer_package
GN 目标中,将fidl_protocol_fuzzer()
目标添加到fuzzers = [ ... ]
。
实现细节
FIDL 服务器实现模糊测试目标的大部分是 fidlgen
生成的 C++ 代码,该代码预计会使用少量 C 符号向 FIDL 服务器实现提供 API。生成的代码
包含一个绑定到其初始线程的全局 async::Loop
,并在
每次运行模糊测试目标时建立 FIDL 连接。LibFuzzer
会使用不同的输入反复调用同一模糊测试目标。生成的模糊测试目标代码将:
- 调用
fuzzer_init()
,初始化要进行模糊测试的服务器。 - 实例化一对
zx::channel
。 - 初始化适当类型的
fidl::InterfacePtr
,绑定到通道端和循环的 调度程序。 - 调用
fuzzer_connect(raw_server_channel_handle, loop->dispatcher())
,与服务器建立连接,并允许服务器选择使用与客户端相同的分派器(如果其 API 与此类架构兼容)(请参阅线程相关注意事项)。 - 通过其
fidl::InterfacePtr
调用方法。 - 将其
async::Loop
设置为 run-until-idle。 - 通过
zx::event
与方法的回调同步。 - 调用
fuzzer_disconnect(raw_server_channel_handle, loop->dispatcher())
,使服务器能够 清理连接中的一端 - 调用
fuzzer_clean_up()
来拆解服务器实例。
将模糊测试目标输入分配给 FIDL 消息
概括地说,前两个字节用于从这些选项中选择协议和方法对。
(在 FIDL 源文件中定义的)。如果 FIDL 文件包含许多协议,但
一个已在模糊测试工具中启用,那么要发现有意义的输入,依赖于 LibFuzzer
的覆盖率引导
推断出某种形式的前字节会导致模糊测试工具几乎不执行任何代码。
识别协议和方法对后,剩余字节将按如下方式划分:
- 每种类型都有一个 trait,用于定义其所需的最小字节数。
- 如果输入的字节不足,则模糊测试目标会立即退出。
- 否则“Slack”如果字节数超过方法参数所需的最小值,则将其除以
均匀分配参数,并调用每种类型的分配 trait 来构造
相应类型(最多使用
MinForParam + SlackForParam
个字节)。
在分配 trait 细节方面,集合和数字类型具有相对自然的特点, 根据要转换为对象的字节集合进行解释。句柄会被视为数字类型,因此当服务器尝试使用它们时,可能会导致错误。
关于线程处理的说明
{#a-note-about-threading}
强烈建议将模糊测试目标保持为单线程。也就是说,在 C++ 中使用 ServerProviderDispatcherMode::kFromCaller
,或在 C 中使用传递给 fuzzer_connect
的 async_dispatcher_t*
。这是首选方式,因为它可以提高
将始终可重现。
使用 AFL 对 FIDL 托管工具进行模糊测试
此外,过去的实验性工作使 FIDL 编译器进行了模糊测试, afl-fuzz。
构建 afl-fuzz
下载并构建该文件,然后将 export AFL_PATH
设为下载和构建时使用的任何路径。
修补解析器以免陷入无效语法
afl-fuzz 认为崩溃很有意思,但解析器目前调用了 __builtin_trap()
。在 parser.h 中移除该行(位于 Parser::Fail()
方法中)。
使用 afl-fuzz 的插桩构建 fidl
工具
清除所有现有 build,然后使用 afl-fuzz 编译器封装容器进行构建。
cd $ZIRCON_DIR
rm -fr build-x86
PATH=$PWD/prebuilt/downloads/clang+llvm-x86_64-linux/bin/:$PATH:$AFL_PATH make \
build-x86/tools/fidl HOST_TOOLCHAIN_PREFIX=afl-
如果您不是在 x86 Linux 等平台上构建,请进行相应调整。
运行模糊测试工具
解析器包含一些可用作输入的示例。
随着 FIDL 的采用,我们可以扩展输入,以涵盖所有不同的协议。
但在我们的树中进行了声明,但目前我们使用的是 tools/fidl/examples
中的内容。
$AFL_PATH/afl-fuzz -i tools/fidl/examples -o fidl-fuzz-out build-x86/tools/fidl dump '@@'
结果
在相当快的机器上,针对 2017 年 5 月初的源代码运行了两天的模糊测试,没有发生崩溃或挂起。它已运行超过 3 亿次。