trailofbits/test-fuzz
GitHub: trailofbits/test-fuzz
一款通过复用 Rust 测试设施自动生成语料库和测试具的 AFL 模糊测试工具。
Stars: 198 | Forks: 25
# test-fuzz
`test-fuzz` 是一个 Cargo 子命令和 Rust 宏的集合,用于自动化某些与 [`afl.rs`] 模糊测试相关的任务,包括:
- 生成模糊测试语料库
- 实现模糊测试测试具 (harness)
`test-fuzz` 部分利用 Rust 的测试设施来完成这些任务。例如,为了生成模糊测试语料库,`test-fuzz` 会在 `cargo test` 调用期间记录目标函数每次被调用时的参数。同样,`test-fuzz` 将模糊测试测试具实现为 `cargo-test` 生成的二进制文件中的一个额外测试。这种与 Rust 测试设施的紧密集成正是 **`test`**`-fuzz` 名称的由来。
**目录**
1. [安装]
2. [概述]
3. [组件]
- [`test_fuzz` 宏]
- [`test_fuzz_impl` 宏]
- [`cargo test-fuzz` 命令]
- [便捷函数和宏]
4. [`test-fuzz` 包特性]
5. [自动生成的语料库文件]
6. [环境变量]
7. [限制]
8. [技巧与窍门]
9. [语义版本控制策略]
10. [许可证]
## 安装
使用以下命令安装 `cargo-test-fuzz` 和 [`afl.rs`]:
```
cargo install cargo-test-fuzz cargo-afl
```
## 概述
使用 `test-fuzz` 进行模糊测试本质上是三个步骤:\*
1. **确定模糊测试目标**:
- 将以下 `dependencies` 添加到目标 crate 的 `Cargo.toml` 文件中:
serde = "*"
test-fuzz = "*"
- 在目标函数前添加 [`test_fuzz`] 宏:
#[test_fuzz::test_fuzz]
fn foo(...) {
...
}
2. **生成语料库**,通过运行 `cargo test`:
cargo test
3. **对目标进行模糊测试**,通过运行 [`cargo test-fuzz`]:
cargo test-fuzz foo
\* 重启后可能需要一个额外的预备步骤:
```
cargo afl system-config
```
请注意,上述命令在内部运行 `sudo`。因此,系统可能会提示您输入密码。
## 组件
### `test_fuzz` 宏
在函数前添加 `test_fuzz` 宏表示该函数是一个模糊测试目标。
`test_fuzz` 宏的主要作用是:
- 向目标添加检测代码,以便在每次调用目标时序列化其参数并将其写入语料库文件。该检测代码由 `#[cfg(test)]` 保护,因此仅在运行测试时生成语料库文件(但是,请参阅下面的 [`enable_in_production`])。
- 添加一个测试,用于从标准输入读取并反序列化参数,然后将目标应用于这些参数。该测试会检查由 [`cargo test-fuzz`] 设置的环境变量,以便在正常的 `cargo test` 调用期间测试不会因尝试从标准输入读取而阻塞。该测试封装在一个模块中,以减少名称冲突的可能性。目前,模块的名称为 `target_fuzz`,其中 `target` 是目标的名称(但是,请参阅下面的 [`rename`])。
#### 参数
##### `bounds = "where_predicates"`
对用于序列化/反序列化参数的结构体施加 `where_predicates`(例如,trait 约束)。这可能是必要的,例如,如果目标的参数类型是关联类型。有关示例,请参阅本仓库中的 [associated_type.rs]。
##### `generic_args = "parameters"`
在模糊测试时将 `parameters` 用作目标的类型参数。示例:
```
#[test_fuzz(generic_args = "String")]
fn foo
(x: &T) {
...
}
```
注意:对于类型参数的**每一个**实例化,目标的参数都必须是可序列化的。但仅当目标使用 `parameters` 实例化时,才要求目标的参数是可反序列化的。
##### `impl_generic_args = "parameters"`
在模糊测试时将 `parameters` 用作目标的 `Self` 类型参数。示例:
```
#[test_fuzz_impl]
impl for Foo {
#[test_fuzz(impl_generic_args = "String")]
fn bar(&self, x: &T) {
...
}
}
```
注意:对于 `Self` 类型参数的**每一个**实例化,目标的参数都必须是可序列化的。但仅当目标的 `Self` 使用 `parameters` 实例化时,才要求目标的参数是可反序列化的。
##### `convert = "X, Y"`
序列化目标参数时,使用 `Y` 对 `From` 的实现将类型 `X` 的值转换为类型 `Y`,或使用 `Y` 对非标准 trait `test_fuzz::FromRef` 的实现将类型 `&X` 的值转换为类型 `Y`。反序列化时,使用 `Y` 对非标准 trait `test_fuzz::Into` 的实现将这些值转换回类型 `X`。
也就是说,使用 `convert = "X, Y"` 必须伴随某些实现。如果 `X` 实现了 [`Clone`],那么 `Y` 可以实现以下内容:
```
impl From for Y {
fn from(x: X) -> Self {
...
}
}
```
如果 `X` 没有实现 [`Clone`],那么 `Y` 必须实现以下内容:
```
impl test_fuzz::FromRef for Y {
fn from_ref(x: &X) -> Self {
...
}
}
```
此外,`Y` 必须实现以下内容(无论 `X` 是否实现 [`Clone`]):
```
impl test_fuzz::Into for Y {
fn into(self) -> X {
...
}
}
```
`test_fuzz::Into` 的定义与 [`std::convert::Into`] 相同。使用非标准 trait 的原因是为了避免因标准 trait 的覆盖实现而可能产生的冲突。
##### `enable_in_production`
在非运行测试时生成语料库文件,前提是设置了环境变量 [`TEST_FUZZ_WRITE`]。默认情况是仅在运行测试时生成语料库文件,无论是否设置了 [`TEST_FUZZ_WRITE`]。当从其包目录外部运行目标时,请将 [`TEST_FUZZ_MANIFEST_PATH`] 设置为该包的 `Cargo.toml` 文件路径。
**警告**:设置 `enable_in_production` 可能会引入拒绝服务向量。例如,如果对一个使用不同参数调用多次的函数设置此选项,可能会填满磁盘。对 [`TEST_FUZZ_WRITE`] 的检查旨在针对这种可能性提供一些防御。尽管如此,在使用此选项之前请仔细考虑。
##### `execute_with = "function"`
而不是直接调用目标:
- 构造一个类型为 `FnOnce() -> R` 的闭包,其中 `R` 是目标的返回类型,以便调用该闭包会调用目标;
- 用该闭包调用 `function`。
以这种方式调用目标允许 `function` 设置调用的环境。这可能很有用,例如,用于模糊测试 [Substrate externalities]。
##### `no_auto_generate`
不要尝试为目标[自动生成语料库文件]。
##### `only_generic_args`
在运行测试时记录目标的泛型参数,但不生成语料库文件且不实现模糊测试测试具。当目标是泛型函数但不清楚应使用哪些类型参数进行模糊测试时,这会很有用。
预期的工作流程是:启用 `only_generic_args`,然后运行 `cargo test`,接着运行 `cargo test-fuzz --display generic-args`。生成的某个泛型参数可能可用作 `generic_args` 的 `parameters`。类似地,由 `cargo test-fuzz --display impl-generic-args` 生成的泛型参数可能可用作 `impl_generic_args` 的 `parameters`。
但是请注意,仅仅因为目标在测试期间使用了某些参数调用,并不意味着在使用这些参数时目标的参数是可序列化/反序列化的。`--display generic-args`/`--display impl-generic-args` 的结果仅具有提示性。
##### `rename = "name"`
将目标视为名为 `name`,以便将模块添加到封闭作用域时使用。`test_fuzz` 宏的展开会向封闭作用域添加一个模块定义。默认情况下,模块命名如下:
- 如果目标未出现在 `impl` 块中,则模块名为 `target_fuzz__`,其中 `target` 是目标的名称。
- 如果目标出现在 `impl` 块中,则模块名为 `path_target_fuzz__`,其中 `path` 是 `impl` 的 `Self` 类型转换为蛇形命名并用 `_` 连接后的路径。
但是,使用此选项会导致模块命名为 `name_fuzz__`。示例:
```
#[test_fuzz(rename = "bar")]
fn foo() {}
// Without the use of `rename`, a name collision and compile error would result.
mod foo_fuzz__ {}
```
#### 函数参数上的 Serde 字段属性
`test_fuzz` 宏允许将 [Serde 字段属性] 应用于函数参数。这提供了另一种处理困难类型的工具。
以下是一个示例。`Context` 无法派生 `serde::Serialize` 和 `serde::Deserialize` trait,因为它包含一个 `Mutex`。但是,`Context` 实现了 `Default`。因此,将 `#[serde(skip)]` 应用于 `Context` 参数会导致其在序列化时被跳过,并在反序列化时采用其默认值。
```
use std::sync::Mutex;
// Traits `serde::Serialize` and `serde::Deserialize` cannot be derived for `Context` because it
// contains a `Mutex`.
#[derive(Default)]
struct Context {
lock: Mutex<()>,
}
impl Clone for Context {
fn clone(&self) -> Self {
Self {
lock: Mutex::new(()),
}
}
}
#[test_fuzz::test_fuzz]
fn target(#[serde(skip)] context: Context, x: i32) {
assert!(x >= 0);
}
```
请注意,当 Serde 字段属性应用于参数时,`test_fuzz` 宏不会对该参数执行其他[转换]。
### `test_fuzz_impl` 宏
每当在 `impl` 块中使用 [`test_fuzz`] 宏时,
该 `impl` 前必须加上 `test_fuzz_impl` 宏。示例:
```
#[test_fuzz_impl]
impl Foo {
#[test_fuzz]
fn bar(&self, x: &str) {
...
}
}
```
此要求的原因如下。[`test_fuzz`] 宏的展开会向封闭作用域添加一个模块定义。但是,模块定义不能出现在 `impl` 块内。在 `impl` 前添加 `test_fuzz_impl` 宏会导致模块被添加到 `impl` 块之外。
如果您看到类似以下的错误,很可能意味着缺少 `test_fuzz_impl` 宏的使用:
```
error: module is not supported in `trait`s or `impl`s
```
`test_fuzz_impl` 目前没有选项。
### `cargo test-fuzz` 命令
`cargo test-fuzz` 命令用于与模糊测试目标交互,并操作其语料库、崩溃、挂起和工作队列。示例调用包括:
1. 列出模糊测试目标
cargo test-fuzz --list
2. 显示目标 `foo` 的语料库
cargo test-fuzz foo --display corpus
3. 对目标 `foo` 进行模糊测试
cargo test-fuzz foo
4. 重放为目标 `foo` 发现的崩溃
cargo test-fuzz foo --replay crashes
#### 用法
```
Usage: cargo test-fuzz [OPTIONS] [TARGETNAME] [-- ...]
Arguments:
[TARGETNAME] String that fuzz target's name must contain
[ARGS]... Arguments for the fuzzer
Options:
--backtrace Display backtraces
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
consolidate all targets, use --consolidate-all
--coverage 标签:afl.rs, Cargo子命令, Fuzzing, Rust, Rust安全, SOC Prime, 代码安全审计, 单元测试, 可视化界面, 宏, 开发工具, 测试辅助, 网络流量审计, 语料库生成, 软件测试工具, 通知系统, 通知系统