Google Fuzzing项目解析:为什么我们需要模糊测试
什么是模糊测试
模糊测试(Fuzzing)是一种通过生成随机或半随机数据来测试API接口的自动化测试技术。它主要分为两种形式:
- 基于变异的模糊测试:通过对现有测试样本(称为测试语料库)进行变异来创建测试数据
- 基于生成的模糊测试:根据输入模型生成全新的测试数据
其中,引导式模糊测试是变异测试的重要扩展。引导式模糊测试器在测试新变异的输入时会采用反馈机制。如果某个输入产生了新的信号(如增加了代码覆盖率),它就会被永久添加到测试语料库中。随着时间的推移,语料库不断增长,从而提高了目标程序的测试覆盖率。
模糊测试的价值所在
模糊测试在以下场景中特别有价值:
- 安全性:处理来自不可信来源输入的软件
- 正确性:检查两个复杂算法等价性的场景
- 稳定性:验证高吞吐量API(如解压缩器)的稳定性,即使所有输入都是可信的
模糊测试之所以重要,是因为它能发现那些与项目需求无关的编程错误。这些错误往往是开发者很少主动测试的类型,如内存泄漏或缓冲区问题,但它们恰恰是导致安全问题和可靠性问题的罪魁祸首。
需要注意的是,模糊测试不能替代针对功能需求的显式测试。开发者仍应编写适当的单元/集成/系统测试,然后再考虑是否适用模糊测试作为补充。
模糊测试的持续运行
模糊测试需要持续运行才能发挥最大价值。在Google内部,一旦提交了模糊测试目标,测试基础设施就会7×24小时不间断地运行它。随着语料库的不断增长,它既可以对稳定代码进行深度测试,也可以对新变更进行回归测试。
模糊测试能发现哪些类型的错误
模糊测试特别擅长发现以下类型的错误:
-
C/C++特有错误:
- 释放后使用、缓冲区问题
- 未初始化内存使用
- 内存泄漏
-
算术错误:
- 除零错误、整数/浮点溢出、无效位移操作
-
简单崩溃:
- 空指针解引用、未捕获异常
-
并发错误:
- 数据竞争、死锁
-
资源使用错误:
- 内存耗尽、死循环、无限递归(栈溢出)
-
逻辑错误:
- 同一协议两种实现间的差异
- 往返一致性错误(如压缩输入后解压,与原始输入比较)
- 断言失败
这些错误类型正是攻击者用来制造问题的常见目标,从拒绝服务攻击到远程代码执行都有可能。
适合模糊测试的项目类型
模糊测试在以下类型的项目中特别有效:
-
处理不可信或复杂输入的项目:
- 各种解析器(XML、PDF、TrueType等)
- 媒体编解码器(音频、视频、光栅和矢量图像等)
- 网络协议、RPC库(如gRPC)
- 网络扫描器
- 加密库(如BoringSSL、OpenSSL)
- 压缩库(ZIP、GZIP、BZIP2、Brotli等)
- 编译器和解释器(PHP、Perl、Python、Go、Clang等)
- 处理protobuf的服务/库
- 正则表达式匹配器(PCRE、RE2、libc)
- 文本/UTF处理(ICU)
- 数据库(SQLite)
- 浏览器
- 文本编辑器/处理器(Vim、OpenOffice)
-
操作系统内核(如Linux)、驱动程序、监控程序和虚拟机
-
用户界面(如Chrome UI)
模糊测试的成功案例
历史上,模糊测试在发现上述类型项目中的长期存在错误方面极为有效。一些著名的成功案例包括:
- AFL发现的错误
- libFuzzer发现的错误
- syzkaller发现的错误
- go-fuzz发现的错误
- Honggfuzz发现的错误
- Chrome中ClusterFuzz发现的错误
- OSS-Fuzz发现的错误
- Facebook的Sapienz(UI模糊测试)
这些案例总计发现了数万个错误,证明了模糊测试在现代软件开发中的重要价值。
通过Google Fuzzing项目提供的工具和方法,开发者可以更有效地发现和修复这些潜在问题,提高软件的安全性和可靠性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考