提升Go-Fuzz的技术状态
什么是go-fuzz?
go-fuzz通过向程序提供随机输入并监控错误来发现软件缺陷。它包含两个核心组件:go-fuzz和go-fuzz-build。go-fuzz-build负责源代码插桩,目标程序经插桩编译后,生成的二进制文件由go-fuzz用于模糊测试活动。
用户首先对工具进行源代码插桩,以提取运行时覆盖率等信息。随后go-fuzz使用一组初始输入执行程序,这些输入在每次交互中发生变异,以增加覆盖率并触发导致崩溃的异常行为。用户提供的测试套件作为模糊测试入口点,调用待测试函数,并向go-fuzz返回指示输入是否应被丢弃或加入语料库的值。
go-fuzz已成功发现200多个GitHub上标注的缺陷,并在Trail of Bits审计中发现更多问题。
类型别名插桩
缺陷复现
|
|
为成功插桩,需提供测试套件(不要求调用HomeDir函数):
|
|
缺陷分析
崩溃发生在编译插桩代码时。插桩错误地生成了无效代码:
|
|
此处显式转换为fs.FileMode类型,但缺少fs包的导入语句。
根本原因分析
Go 1.16中os.FileMode从类型定义(type FileMode uint32
)变为类型别名(type FileMode = fs.FileMode
)。类型检查器因此要求使用fs.FileMode进行转换,但插桩代码未添加对应导入语句。
修复方案
- 分析插桩文件中所有定义于未导入包的类型
- 为这些类型添加对应包的导入语句
- 通过goimports工具移除未使用的导入
- 确保导入不改变源代码语义(因初始化器仅执行一次)
字典支持
通用模糊测试器在变异二进制数据时表现优异,但对语法敏感程序(如SQL解析器)效果不佳。字典支持允许将语法关键词随机插入输入中。
go-fuzz原有"token capture"技术从插桩代码提取硬编码字符串,但会混入无关词汇(如日志消息)。新方案允许通过-dict
参数提供AFL/libFuzzer格式的字典文件,支持分级控制。
示例SQL字典格式:
|
|
新变异策略
插入重复字节
在输入中随机位置插入随机重复次数的字节,增加字节重复场景的出现概率。
字节洗牌
使用Fisher-Yates算法对随机长度的输入子段进行洗牌。
小端基128编码(LEB128)
改进InsertLiteral策略,支持从源代码提取硬编码整数值并插入输入。LEB128变长编码可高效存储任意大小整数,分为无符号和有符号两种变体。该编码被LLVM、DWARF等广泛使用。
go-fuzz的未来
Go 1.18内置了模糊测试支持,go-fuzz已进入维护阶段。但其作为成熟工具(含trailofbits/go-fuzz-utils等生态工具)仍可在旧项目中发挥作用。希望这些改进能被上游采纳,继续为Go开发者提供灵感。
致谢
感谢Trail of Bits的实习机会和导师Dominik Czarnota、Rory Mackie的指导。