一句话版本
人写测试用例,只会想到自己能想到的情况。模糊测试(fuzzing)让机器自动生成海量的、奇奇怪怪的输入喂给程序,看它会不会崩溃、卡死、或者违反某个规则。 现代模糊测试是覆盖率引导的:它会观察每个输入让程序走了哪些代码路径,保留能走到新路径的输入并在此基础上继续变异,像一个不知疲倦、越来越聪明的测试员。谷歌的 OSS-Fuzz 已经用这种方法在开源项目里发现了上万个安全漏洞。
全文分三层:
- 通俗理解:让一万只猴子乱按键盘,但只奖励发现新房间的猴子
- 实际用处:AFL、OSS-Fuzz、Heartbleed、智能合约的不变量测试、AI 辅助
- 原理与工程要点:覆盖率引导、怎么写测试入口、检测器、什么样的代码适合模糊测试
第一层:通俗理解
1.1 一万只猴子,只奖励发现新房间的
想象一栋迷宫般的大楼,有些房间里藏着炸弹(bug)。你派一万只猴子进去乱跑:
- 纯随机:大部分猴子在门口转圈,很少有能走到深处的;
- 覆盖率引导:每当一只猴子走进了一个从没人去过的房间,就记下它的路线;之后的猴子从这些路线出发,再随机多走几步。
这样,探索的范围会越来越深,最终走到人类测试员想不到的角落里。
1.2 它擅长找什么
| 类型 | 例子 |
|---|---|
| 崩溃 | 空指针、数组越界、除以零 |
| 内存安全问题 | 缓冲区溢出、释放后使用(配合检测器) |
| 卡死 | 无限循环、超长耗时 |
| 违反规则 | 解析后再序列化结果不一致;"余额永远不为负"被打破 |
特别擅长测试解析不可信输入的代码:文件格式、网络协议、压缩、图片解码、正则引擎。
第二层:实际用处
- AFL(2013):Michał Zalewski 发布的 American Fuzzy Lop,开创了实用的覆盖率引导模糊测试,在大量知名软件中发现了漏洞。之后有 libFuzzer、AFL++、honggfuzz 等。
- Heartbleed(2014):OpenSSL 的这个严重漏洞由两组人独立发现,其中一组(Codenomicon 公司)正是在用自己的模糊测试工具测试 TLS 实现时发现的。
- OSS-Fuzz(2016 年起):谷歌为关键开源项目提供的免费持续模糊测试服务。截至 2023 年,它已在一千多个项目中帮助发现并修复了一万多个安全漏洞和数万个其他 bug。
- 智能合约:Foundry、Echidna 等工具对合约做"不变量模糊测试":随机生成调用序列,检查"资产 ≥ 负债"一类的性质是否始终成立(见《合约审计与形式化验证》)。
- AI 辅助:近年开始用大模型自动编写模糊测试入口、分析崩溃,甚至直接寻找漏洞。谷歌在 2024 年报告了其 AI 代理在 SQLite 中发现了一个此前未知的可利用漏洞。
第三层:原理与工程要点
3.1 覆盖率引导的循环
编译时插入轻量的"探针",记录程序经过了哪些分支。一个输入只要触发了新的分支组合,就被视为"有趣",加入语料库。
3.2 写一个测试入口
模糊测试需要一个"入口函数":接收一段任意字节,喂给被测代码。以 libFuzzer 风格为例:
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
parse_my_format(data, size); // 被测函数:不能崩溃,不能违反断言
return 0;
}
好的入口:
- 快:每秒能跑成千上万次;
- 确定:同样的输入总是同样的结果;
- 聚焦:只测一个解析器或一个模块。
还可以加入性质检查,比如"解析后再序列化,应该得到相同的内容"(往返测试),或者"两种实现的输出必须一致"(差分测试,见《多源交叉验证》)。
3.3 检测器(sanitizer)
很多内存错误不会立即导致崩溃,而是悄悄破坏数据。编译时开启检测器,能让这些问题在发生的那一刻就被抓住:
| 检测器 | 找什么 |
|---|---|
| AddressSanitizer | 越界读写、释放后使用 |
| UndefinedBehaviorSanitizer | 整数溢出、非法移位等未定义行为 |
| MemorySanitizer | 使用未初始化的内存 |
3.4 种子语料
从一些合法的样本输入开始(比如几个正常的图片文件、几条正常的协议消息),比从空白开始效率高得多。语料库要持续保存,作为回归测试的一部分。
3.5 和其他测试的关系
| 测试 | 回答的问题 |
|---|---|
| 单元测试 | 我想到的情况对不对? |
| 模糊测试 | 我没想到的情况会不会出事? |
| 基于性质的测试 | 某条规则在大量随机输入下是否都成立?(模糊测试的近亲) |
| 形式化验证 | 某条规则在所有输入下是否都成立? |
模糊测试最适合持续运行:接入 CI,每次代码变更都跑一段时间,或者像 OSS-Fuzz 那样全天候运行。
总结
| 层次 | 要点 |
|---|---|
| 直觉 | 海量随机输入,只奖励发现新路径的那些 |
| 擅长 | 解析不可信输入的代码;崩溃、内存错误、卡死、性质违反 |
| 关键 | 覆盖率引导 + 检测器 + 好的种子语料 |
| 实践 | 写快而确定的入口;加入往返和差分检查;持续运行 |