go原生fuzz能暴露panic、越界等缺陷,但要求函数纯输入驱动、无副作用且种子精准;需严格匹配命名、签名、文件后缀及f.add()/f.fuzz()调用规范,否则静默失效。

Go 原生 fuzz 能直接暴露解析函数里的 panic、越界、无限循环和逻辑绕过,但前提是函数得是纯输入驱动、无副作用、且种子喂得准——否则它可能跑十分钟还在试 "a" 和 "aa"。
函数签名和文件位置必须严格匹配规则
Go 不会报错,只会静默跳过不合规的 fuzz 函数。常见失效组合:
-
FuzzParseJSON✅;fuzzParseJSON❌(首字母小写);Fuzzparsejson❌(驼峰断裂) - 参数必须是
*testing.F,不是*testing.T或自定义结构体指针 - 文件名必须含
_test.go后缀,哪怕只是空测试文件也不行;放在main.go或普通.go里就完全不可见 - 函数体内必须同时调用
f.Add()和f.Fuzz();只写一个,fuzzer 就不会启动
种子 f.Add() 不是可选的,而是收敛关键
没种子,fuzzer 对结构化输入(如 JSON、URL、协议帧)基本无效。它不会自动构造 {"arr":[{},{},]} 这类嵌套,只会变异 ASCII 字符串。
- 合法最小单元必须有:
f.Add([]byte(`{}`))、f.Add([]byte(`{"a":1}`))—— 让 parser 先跑通主路径 - 非法格式要直击语法断点:
f.Add([]byte(`{"name":`))(缺右括号)、f.Add([]byte{0x00, 0xff})(二进制脏字节) - 边界值别只塞
"123":f.Add([]byte(strings.Repeat("x", 1000000)))(超长字符串)、f.Add([]byte(`{"a":` + strings.Repeat("[],", 1000) + "[]}"))(深度嵌套) - Unicode 控制字符很有效:
f.Add([]byte(`{"name":"\u202e\u0000"}`)),能快速暴露 UTF-8 校验缺失或渲染逻辑漏洞
f.Fuzz() 闭包里只能传基础类型,结构体得拆解
你不能写 f.Fuzz(func(t *testing.T, u User) { ... }) —— 编译失败。fuzzer 只支持原始输入驱动,不支持结构体直接注入。
- 正确方式是接收
[]byte或string,再手动调用json.Unmarshal()或协议解析器 - 若想单独测某个字段(比如 timeout),需另起一个 fuzz 函数:
f.Fuzz(func(t *testing.T, tmo int64) { ... }),并用f.Add(int64(math.MaxInt64))等极值喂种 - 切忌在闭包里做耗时操作:先快速检查长度、括号平衡、基础格式,再进全量解析,否则迭代太慢
- 别依赖“没 panic 就安全”:整数溢出(如
int64 + 1)会静默回绕,要用math.AddOverflow主动检测
崩溃复现不了?重点盯 goroutine 和 recover
原生 fuzz 只捕获主 goroutine 的 panic。很多解析器启了子 goroutine 处理流式数据,panic 被吞掉,crashers 目录就为空。
- 在
f.Fuzz()闭包开头加defer func() { if r := recover(); r != nil { panic(r) } }(),强制透出子 goroutine panic - 被测函数涉及并发(如启动
http.Server)时,必须配t.Parallel()和显式超时,否则 fuzz 卡死 - 用
-fuzzcachedir=fuzzcache指定缓存目录,避免系统临时目录被清理导致 crash 用例丢失 -
os.Exit(1)或SIGSEGV不触发保存逻辑——这类得换go-fuzz工具链
最常被忽略的是:fuzz 不是黑盒探针,它只对「输入决定行为」的函数有效;一旦函数读环境变量、打开文件、调用 time.Now(),每次运行结果就不一致,crash 输入也无法最小化复现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











