提升Go-Fuzz技术:类型别名修复、字典支持与新变异策略

本文详细介绍了对Go语言模糊测试工具go-fuzz的三项关键技术改进:修复类型别名导致的编译错误、集成语法感知的字典支持机制,以及新增三种高效输入变异策略,显著提升Go项目的漏洞发现能力。

提升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审计中发现更多问题。

类型别名插桩

缺陷复现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package homedir
import ( "os"; "fmt" )

func HomeDir() {
    p := "text.txt"
    info, _ := os.Stat(p)
    if info.Mode().Perm()&(1<<(uint(7))) & 1 != 0 {
        fmt.Println("test")
    }
}

为成功插桩,需提供测试套件(不要求调用HomeDir函数):

1
2
package homedir
func Fuzz(data []byte) int { return 1 }

缺陷分析

崩溃发生在编译插桩代码时。插桩错误地生成了无效代码:

1
2
//line homedir.go:13
__gofuzz_v1 := fs.FileMode(info.Mode().Perm() & (1 << 7))

此处显式转换为fs.FileMode类型,但缺少fs包的导入语句。

根本原因分析

Go 1.16中os.FileMode从类型定义(type FileMode uint32)变为类型别名(type FileMode = fs.FileMode)。类型检查器因此要求使用fs.FileMode进行转换,但插桩代码未添加对应导入语句。

修复方案

  1. 分析插桩文件中所有定义于未导入包的类型
  2. 为这些类型添加对应包的导入语句
  3. 通过goimports工具移除未使用的导入
  4. 确保导入不改变源代码语义(因初始化器仅执行一次)

字典支持

通用模糊测试器在变异二进制数据时表现优异,但对语法敏感程序(如SQL解析器)效果不佳。字典支持允许将语法关键词随机插入输入中。

go-fuzz原有"token capture"技术从插桩代码提取硬编码字符串,但会混入无关词汇(如日志消息)。新方案允许通过-dict参数提供AFL/libFuzzer格式的字典文件,支持分级控制。

示例SQL字典格式:

1
2
3
4
false]
function_abs=" abs(1)"
keyword_ADD="ADD"
keyword_ALTER="ALTER"

新变异策略

插入重复字节

在输入中随机位置插入随机重复次数的字节,增加字节重复场景的出现概率。

字节洗牌

使用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的指导。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计