name: cargo-fuzz type: fuzzer description: > cargo-fuzz 是使用 Cargo 的 Rust 项目的默认模糊测试工具。 用于通过 libFuzzer 后端进行 Rust 代码的模糊测试。
cargo-fuzz
cargo-fuzz 是使用 Cargo 时模糊测试 Rust 项目的首选工具。它使用 libFuzzer 作为后端,并提供一个方便的 Cargo 子命令,自动为你的 Rust 项目启用相关编译标志,包括对地址消毒器等消毒器的支持。
何时使用
cargo-fuzz 目前是使用 Cargo 的 Rust 项目的主要和最成熟的模糊测试解决方案。
| 模糊测试工具 | 最佳适用场景 | 复杂度 |
|---|---|---|
| cargo-fuzz | 基于 Cargo 的 Rust 项目,快速设置 | 低 |
| AFL++ | 多核模糊测试,非 Cargo 项目 | 中 |
| LibAFL | 自定义模糊测试器,研究,高级用例 | 高 |
选择 cargo-fuzz 当:
- 你的项目使用 Cargo(必需)
- 你想要简单、快速的设置,配置最少
- 你需要集成消毒器支持
- 你在模糊测试带有或不带有不安全块的 Rust 代码
快速开始
#![no_main]
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
your_project::check_buf(data);
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
初始化和运行:
cargo fuzz init
# 编辑 fuzz/fuzz_targets/fuzz_target_1.rs 以添加你的测试体
cargo +nightly fuzz run fuzz_target_1
安装
cargo-fuzz 需要 nightly Rust 工具链,因为它使用仅 nightly 可用的功能。
先决条件
- 通过 rustup 安装的 Rust 和 Cargo
- Nightly 工具链
Linux/macOS
# 安装 nightly 工具链
rustup install nightly
# 安装 cargo-fuzz
cargo install cargo-fuzz
验证
cargo +nightly --version
cargo fuzz --version
编写测试体
项目结构
cargo-fuzz 在代码结构为库 crate 时效果最佳。如果你有二进制项目,将 main.rs 拆分为:
src/main.rs # 入口点(主函数)
src/lib.rs # 要模糊测试的代码(公共函数)
Cargo.toml
初始化模糊测试:
cargo fuzz init
这会创建:
fuzz/
├── Cargo.toml
└── fuzz_targets/
└── fuzz_target_1.rs
测试体结构
#![no_main]
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
// 1. 如果需要,验证输入大小
if data.is_empty() {
return;
}
// 2. 用模糊数据调用目标函数
your_project::target_function(data);
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
测试体规则
| 做 | 不要 |
|---|---|
| 将代码结构为库 crate | 将所有内容保留在 main.rs 中 |
使用 fuzz_target! 宏 |
编写自定义主函数 |
优雅地处理 Result::Err |
在预期错误时恐慌 |
| 保持测试体确定性 | 使用随机数生成器 |
另请参阅: 对于详细的测试体编写技术和使用
arbitrary库进行结构感知模糊测试,请参阅 fuzz-harness-writing 技术技能。
结构感知模糊测试
cargo-fuzz 集成了 arbitrary 库用于结构感知模糊测试:
// 在你的库 crate 中
use arbitrary::Arbitrary;
#[derive(Debug, Arbitrary)]
pub struct Name {
data: String
}
// 在你的模糊测试目标中
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: your_project::Name| {
data.check_buf();
});
添加到你的库的 Cargo.toml:
[dependencies]
arbitrary = { version = "1", features = ["derive"] }
运行活动
基本运行
cargo +nightly fuzz run fuzz_target_1
不带消毒器(安全 Rust)
如果你的项目不使用不安全 Rust,禁用消毒器以获得 2 倍性能提升:
cargo +nightly fuzz run --sanitizer none fuzz_target_1
检查你的项目是否使用不安全代码:
cargo install cargo-geiger
cargo geiger
重新执行测试用例
# 运行特定测试用例(例如,崩溃)
cargo +nightly fuzz run fuzz_target_1 fuzz/artifacts/fuzz_target_1/crash-<hash>
# 运行所有语料库条目而不进行模糊测试
cargo +nightly fuzz run fuzz_target_1 fuzz/corpus/fuzz_target_1 -- -runs=0
使用字典
cargo +nightly fuzz run fuzz_target_1 -- -dict=./dict.dict
解释输出
| 输出 | 含义 |
|---|---|
NEW |
发现了新的增加覆盖率的输入 |
pulse |
周期性状态更新 |
INITED |
模糊测试器初始化成功 |
| 带有堆栈跟踪的崩溃 | 发现错误,保存到 fuzz/artifacts/ |
语料库位置:fuzz/corpus/fuzz_target_1/
崩溃位置:fuzz/artifacts/fuzz_target_1/
消毒器集成
地址消毒器(ASan)
ASan 默认启用,检测内存错误:
cargo +nightly fuzz run fuzz_target_1
禁用消毒器
对于纯安全 Rust(你的代码或依赖中没有不安全块):
cargo +nightly fuzz run --sanitizer none fuzz_target_1
性能影响: ASan 添加约 2 倍开销。禁用以提升模糊测试速度。
检查不安全代码
cargo install cargo-geiger
cargo geiger
另请参阅: 对于详细的消毒器配置、标志和故障排除,请参阅 address-sanitizer 技术技能。
覆盖率分析
cargo-fuzz 集成 Rust 的覆盖率工具以分析模糊测试效果。
先决条件
rustup toolchain install nightly --component llvm-tools-preview
cargo install cargo-binutils
cargo install rustfilt
生成覆盖率报告
# 从语料库生成覆盖率数据
cargo +nightly fuzz coverage fuzz_target_1
创建覆盖率生成脚本:
cat <<'EOF' > ./generate_html
#!/bin/sh
if [ $# -lt 1 ]; then
echo "错误:模糊测试目标的名称是必需的。"
echo "用法:$0 fuzz_target [sources...]"
exit 1
fi
FUZZ_TARGET="$1"
shift
SRC_FILTER="$@"
TARGET=$(rustc -vV | sed -n 's|host: ||p')
cargo +nightly cov -- show -Xdemangler=rustfilt \
"target/$TARGET/coverage/$TARGET/release/$FUZZ_TARGET" \
-instr-profile="fuzz/coverage/$FUZZ_TARGET/coverage.profdata" \
-show-line-counts-or-regions -show-instantiations \
-format=html -o fuzz_html/ $SRC_FILTER
EOF
chmod +x ./generate_html
生成 HTML 报告:
./generate_html fuzz_target_1 src/lib.rs
HTML 报告保存到:fuzz_html/
另请参阅: 对于详细的覆盖率分析技术和系统性覆盖率改进,请参阅 coverage-analysis 技术技能。
高级用法
技巧和窍门
| 技巧 | 为什么有帮助 |
|---|---|
| 从种子语料库开始 | 显著加速初始覆盖率发现 |
对安全 Rust 使用 --sanitizer none |
2 倍性能提升 |
| 定期检查覆盖率 | 识别测试体或种子语料库中的差距 |
| 对解析器使用字典 | 帮助克服魔法值检查 |
| 将代码结构为库 | 对 cargo-fuzz 集成必需 |
libFuzzer 选项
在 -- 后传递选项给 libFuzzer:
# 查看所有选项
cargo +nightly fuzz run fuzz_target_1 -- -help=1
# 设置每次运行超时
cargo +nightly fuzz run fuzz_target_1 -- -timeout=10
# 使用字典
cargo +nightly fuzz run fuzz_target_1 -- -dict=dict.dict
# 限制最大输入大小
cargo +nightly fuzz run fuzz_target_1 -- -max_len=1024
多核模糊测试
# 实验性分支支持(不推荐)
cargo +nightly fuzz run --jobs 1 fuzz_target_1
注意:多核模糊测试功能是实验性的,不推荐。对于并行模糊测试,考虑手动运行多个实例或使用 AFL++。
真实世界示例
示例:ogg 库
ogg 库 解析 Ogg 媒体容器文件。解析器是优秀的模糊测试目标,因为它们处理不可信数据。
# 克隆和初始化
git clone https://github.com/RustAudio/ogg.git
cd ogg/
cargo fuzz init
测试体位于 fuzz/fuzz_targets/fuzz_target_1.rs:
#![no_main]
use ogg::{PacketReader, PacketWriter};
use ogg::writing::PacketWriteEndInfo;
use std::io::Cursor;
use libfuzzer_sys::fuzz_target;
fn harness(data: &[u8]) {
let mut pck_rdr = PacketReader::new(Cursor::new(data.to_vec()));
pck_rdr.delete_unread_packets();
let output = Vec::new();
let mut pck_wtr = PacketWriter::new(Cursor::new(output));
if let Ok(_) = pck_rdr.read_packet() {
if let Ok(r) = pck_rdr.read_packet() {
match r {
Some(pck) => {
let inf = if pck.last_in_stream() {
PacketWriteEndInfo::EndStream
} else if pck.last_in_page() {
PacketWriteEndInfo::EndPage
} else {
PacketWriteEndInfo::NormalPacket
};
let stream_serial = pck.stream_serial();
let absgp_page = pck.absgp_page();
let _ = pck_wtr.write_packet(
pck.data, stream_serial, inf, absgp_page
);
}
None => return,
}
}
}
}
fuzz_target!(|data: &[u8]| {
harness(data);
});
种子语料库:
mkdir fuzz/corpus/fuzz_target_1/
curl -o fuzz/corpus/fuzz_target_1/320x240.ogg \
https://commons.wikimedia.org/wiki/File:320x240.ogg
运行:
cargo +nightly fuzz run fuzz_target_1
分析覆盖率:
cargo +nightly fuzz coverage fuzz_target_1
./generate_html fuzz_target_1 src/lib.rs
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| “需要 nightly” 错误 | 使用稳定工具链 | 使用 cargo +nightly fuzz |
| 模糊测试性能慢 | 对安全 Rust 启用 ASan | 添加 --sanitizer none 标志 |
| “找不到二进制文件” | 没有库 crate | 将代码从 main.rs 移动到 lib.rs |
| 消毒器编译问题 | 错误的 nightly 版本 | 尝试不同的 nightly:rustup install nightly-2024-01-01 |
| 低覆盖率 | 缺少种子语料库 | 添加样本输入到 fuzz/corpus/fuzz_target_1/ |
| 魔法值未找到 | 没有字典 | 创建包含魔法值的字典文件 |
相关技能
技术技能
| 技能 | 使用场景 |
|---|---|
| fuzz-harness-writing | 使用 arbitrary 库进行结构感知模糊测试 |
| address-sanitizer | 理解 ASan 输出和配置 |
| coverage-analysis | 测量和改善模糊测试效果 |
| fuzzing-corpus | 构建和管理种子语料库 |
| fuzzing-dictionaries | 为格式感知模糊测试创建字典 |
相关模糊测试器
| 技能 | 何时考虑 |
|---|---|
| libfuzzer | 用类似工作流程模糊测试 C/C++ 代码 |
| aflpp | 多核模糊测试或非 Cargo Rust 项目 |
| libafl | 高级模糊测试研究或自定义模糊测试器开发 |
资源
Rust Fuzz Book - cargo-fuzz cargo-fuzz 的官方文档,涵盖安装、使用和高级功能。
arbitrary 库文档 Rust 类型自动推导的结构感知模糊测试指南。
cargo-fuzz GitHub 仓库 cargo-fuzz 的源代码、问题跟踪器和示例。