对 FIDL 进行模糊测试

在 Fuchsia 上使用 libFuzzer 对 FIDL 服务器进行模糊测试

Fuchsia 包含对以 FIDL 单元测试样式编写 FIDL fuzzer 的实验性支持。

快速入门指南

//examples/fuzzers/fidl 中定义了一个模糊测试工具示例。如果您 不熟悉模糊测试工具,请参阅概览。如需在 Fuchsia 上使用 libFuzzer 对 FIDL 服务器进行模糊测试,需要使用会生成模糊测试目标的 GN 目标,并编写一些代码来提供要进行模糊测试的服务器实例。

  1. fuzzers = {protocol = "fully.qualified.fidl.ProtocolName"} 添加到您的 fidl() GN 目标。
  2. 根据您使用的语言,执行以下操作:
    • 在 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 以外的状态,则模糊测试工具将进行清理并停止。
  3. 定义 fidl_protocol_fuzzer() GN 目标。指定:
    • fidl = //path/to:fidl_gn_target(上述 fidl() 目标)。
    • protocol = "fully.qualified.fidl.ProtocolName"
    • deps = [... :your_library ...](上面提到的用于定义 fuzzer_... 符号的文件)。
    • fuzzer GN 目标所需的任何其他信息, 服务器。
  4. 在新的或现有的 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_connectasync_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 亿次。