编写模糊测试工具

Fuchsia 的工具链支持使用 LLVM 的 libFuzzer 进行模糊测试。如需针对特定接口创建模糊测试工具,您需要实现一个模糊测试目标函数,该函数使用提供的字节序列来运行该接口。字节序列称为模糊测试工具“输入”。libFuzzer 使用模糊目标函数来搜索导致紧急警报或其他错误的输入。

模糊测试示例代码

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

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 特征。

您还可以创建一个“手动”模糊测试目标函数,用于模拟其他语言中的简单模糊测试目标函数。此函数必须将对字节切片的引用作为其单个参数,不返回任何内容,并且具有 #[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;
  }

使用此库有两个显著的好处:

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

有一个明显的缺点:

  • 由于输入是动态拆分的,因此提供预先存在的语料库更加困难。提供字典仍然可行。

Rust

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

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

  1. 如果需要,请为测试代码使用的类型实现 Arbitrary 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 构建新创建的模糊测试工具。