go fuzz只加载fuzz/fuzzxxx/corpus/下纯小写十六进制命名、utf-8无bom无\r\n的种子文件;f.add()是运行时注入,与磁盘种子互不干扰,且必须与f.fuzz()参数类型严格一致。

种子文件路径和命名必须严格匹配硬编码规则
Go fuzz 只加载 fuzz/FuzzXXX/corpus/ 目录下的文件,且路径大小写、层级、拼写一个字母都不能错。比如函数叫 FuzzParseJSON,就必须是 fuzz/FuzzParseJSON/corpus/,写成 fuzz/fuzzparsejson/corpus/ 或 fuzz/parse/corpus/ 会静默跳过,不报错也不提示。
文件名必须是纯小写十六进制字符串,长度任意但不能含前缀或后缀:例如 00000001 ✅,seed1.txt ❌,SEED01 ❌,00000001.json ❌。内容必须是 UTF-8 编码、无 BOM、无 \r\n(只允许 \n 或无换行),否则加载失败且无日志。
f.Add() 和磁盘种子是两套独立机制,别混用还指望它生效
f.Add() 是运行时注入,值只在本次测试进程内有效,不会写入磁盘,也不会影响后续 go test -fuzz= 的 seed 加载;磁盘种子是启动时一次性读取的原始语料,两者不互通。
常见误操作:
- 写了
f.Add("{}", `{"a":`),却以为这样就能让下次go test -fuzz=自动加载——不会,它只对当前go test进程起作用 - 把种子文件放对了路径,但没调
f.Add(),结果f.Fuzz()闭包里收不到任何初始输入——因为f.Fuzz()依赖f.Add()提供的类型签名来决定变异维度 - 闭包参数是
func(t *testing.T, b []byte),却用f.Add("hello")——类型不匹配,直接 panic
结构化输入(如 JSON)的种子必须覆盖“合法起点”和“典型断裂点”
对解析器类函数,光塞 {} 或 {"a":1} 不够,引擎容易卡在浅层分支。真正有效的种子要能触发不同解析状态机转移:
在 Go 中使用 google/wire 实现编译时依赖注入——wire.NewSet、wire.Build、wire.Bind(接口→实现)、wire.Struct、wire.Value、wire.Interface
- 合法最小单元:
f.Add([]byte(`{}`))、f.Add([]byte(`[]`)) - 语法截断:
f.Add([]byte(`{"key":`))(缺值)、f.Add([]byte(`{"key":1,`))(缺右括号) - 边界字节:
f.Add([]byte(`{"name":"\x00"}`))、f.Add([]byte(`{"name":"\uffff"}`)) - 深度嵌套:
f.Add([]byte(`{"a":` + strings.Repeat("{", 100) + "}" + strings.Repeat("}", 100)))
注意:f.Add() 传 []byte 就必须对应闭包参数是 []byte,不能混用 string;否则运行时报 panic,不是编译错。
数值类种子别只填“123”,要直击溢出与邻域敏感点
整数运算函数的模糊效果,极度依赖你喂进去的临界值。引擎对 fuzz.Int() 的变异虽会尝试边界,但前提是它见过这些点——否则变异永远在“安全区间”打转。
有效做法:
- 显式提供极值:
f.Add(math.MaxInt64, 1)、f.Add(math.MinInt64, -1) - 补上邻域值:
f.Add(math.MaxInt64 - 1)、f.Add(math.MaxInt64 + 1)(后者会回绕,但能触发溢出分支) - 避免非法类型欺骗:
uint32函数就别传负数,否则会被转成极大正数(如-1 → 0xffffffff),绕过你本想测的校验逻辑 - 如果函数接受
int,但实际行为受平台位宽影响(如 32 位 vs 64 位),建议分别加math.MaxInt32 + 1和math.MaxInt64 + 1
最常被忽略的是:种子不是越多越好,而是越“有结构差异”越好。一个 []byte{0xff, 0xfe} 比一百个随机 ASCII 字符串更能撬开二进制解析的隐藏路径。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










