编写模糊测试工具

Fuchsia 的工具链支持使用 LLVM 的 libFuzzer 进行模糊测试。为了针对特定的集群 接口,您需要实现一个模糊测试目标函数,该函数使用 提供的字节序列来练习该接口。字节序列称为 模糊测试工具“input”。libFuzzer 使用模糊测试目标函数来搜索会导致 panic 的输入 或其他错误。

用于模糊测试的示例代码

对于下面的每个示例,假设您想要测试如下代码:

C/C++

class Parser {
  Parser(const std::string &name, uint32_t flags);
  virtual ~Parser();
  int Parse(const uint8 *buf, size_t len);
};

Rust

struct ToyStruct {
    n: u8,
    s: String,
}

fn toy_example(input: ToyStruct) -> Result<u8, &'static str>;

简单模糊目标函数

对于每种语言,模糊测试目标函数都将使用所提供的字节来调用所需代码 进行模糊测试。如果要进行模糊测试的接口记录了对其参数的约束条件,您可以拒绝 输入数据。您也可以忽略返回的错误,因为 是正确的行为。

C/C++

对于 C 和 C++,模糊测试目标函数必须具有签名 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) 并返回 0:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  Parser parser("", 0);
  if (size < 5) {
    return 0;
  }
  parser.Parse(data, size);
  return 0;
}

将此代码放在源文件中,并放在要进行模糊测试的代码旁边,就像进行单元测试一样。对于 例如,上面的代码可能位于 parser-fuzztest.cc 中。

Rust

对于 Rust,建议的方法是使用下一部分中讨论的 Arbitrary trait。

也可以创建“手动”模糊测试目标函数,它是针对简单模糊测试的 定位其他语言的函数。此函数必须将对字节片的引用作为其 单个参数,不返回任何内容,并且具有 #[fuzz] 属性:

use fuzz::fuzz;

#[fuzz]
fn toy_example_u8(input: &[u8]) {
    if input.len() == 0 {
        return
    }
    let n = input[0];
    if let Ok(s) = std::str::from_utf8(input[1:]) {
        let _ = toy_example(ToyStruct{n, s: s.to_string(),});
    }
}

与单元测试一样,可以将此代码与其要测试的代码放在同一文件中。对于 例如,上面的代码可能位于 toy_example/src/lib.rs 中。

支持更复杂的模糊测试类型

每种语言都有相应的实用程序,用于简化更复杂的模糊测试目标函数:

C/C++

LLVM 提供的 FuzzedDataProvider 类可以帮助您 将提供的 data 的部分映射到更复杂的类型。

例如:

  #include <fuzzer/FuzzedDataProvider.h>

  extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    FuzzedDataProvider provider(data, size);
    auto flags = provider.ConsumeIntegral<uint32_t>();
    auto name = provider.ConsumeRandomLengthString();
    Parser parser(name, flags);
    auto buf = provider.ConsumeRemainingBytes<uint8_t>();
    parser.Parse(buf.data(), buf.size());
    return 0;
  }

使用此库有两个显著优势:

  • 首先,它可让您更轻松地快速编写模糊测试工具。
  • 其次,它旨在动态拆分输入,使模糊测试工具可以 根据覆盖率数据高效创建新输入。

但有一个明显的缺点:

  • 由于输入是动态拆分的,因此更难提供预先存在的 corpus 的特征。提供 字典

Rust

您可以创建一个模糊测试目标函数,该函数接受一个或多个具有 Arbitrary 特征的输入 从 arbitrary crate 中获取。建议您使用此方法。

如需编写自动转换任意输入的模糊测试目标函数,请执行以下操作:

  1. 如果需要,请为测试代码使用的类型实现 Arbitrary trait。如果可能的话 推荐的方法是自动派生 trait。否则, “手动”完成按照 crate 的说明操作。

    例如,在 src/lib.rs 中:

    use arbitrary:Arbitrary;
    
    #[derive(Arbitrary)]
    struct ToyStruct { ... }
    
  2. 使用 #[fuzz] 属性创建一个函数,用于传递必要的 参数添加到要测试的代码。

    例如,在您的 src/lib.rs 中:

    use fuzz::fuzz;
    
    #[fuzz]
    fn toy_example_arbitrary(input: ToyStruct) {
        let _ = toy_example(input);
    }
    

接下来,您可以使用 GN 和 Ninja 构建新创建的模糊测试工具。