编写模糊测试工具

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 特征 从 arbitrary crate 中创建一个模糊测试目标函数,该函数接受一个或多个输入。这是推荐的方法。

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

  1. 如果需要,请为测试代码使用的类型实现 Arbitrary 特征。如果可能,建议的方法是自动派生特征。否则,可以按照 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 构建新创建的模糊器。