当前位置: 首页 > 工具软件 > go-fuzz > 使用案例 >

go-fuzz: randomized testing for Go

薛英卫
2023-12-01

Go-fuzz是一个覆盖引导的测试Go包的fuzzing解决方案。Fuzzing主要适用于解析复杂输入(文本和二进制)的包,尤其适用于解析潜在恶意用户输入(例如通过网络接受的任何内容)的系统的强化。

注意:go-fuzz最近增加了对fuzzing go模块的初步支持。有关详细信息,请参阅下面的部分。如果您遇到模块问题,请提交详细信息问题。解决方法可能是通过export GO111MODULE=off禁用模块。
项目网址:https://github.com/dvyukov/go-fuzz

Usage

首先,您需要编写一个这种形式的测试函数:

func Fuzz(data []byte) int

数据是由go-fuzz生成的随机输入,注意在大多数情况下这些生成的输入是无效的。如果模糊器应该在随后的模糊中增加给定输入的优先级,则函数必须返回1(例如,输入在词汇上是正确的并且被成功解析);返回-1,如果输入必须不添加到语料库中,即使给出新的覆盖;否则返回0;保留其他值以供将来使用。

这个Fuzz函数必须在go-fuzz可以import的包中。这意味想要被测试的代码不能在包的main函数中。当然,支持模糊测试internal包。

Fuzz函数的基本形式仅是解析输入,而go-fuzz确保它不会死机、崩溃、分配过多内存或挂起。Fuzz函数还可以执行应用程序级检查,这将使测试更加高效(发现更多的错误)。例如,Fuzz函数可以序列化被成功反序列化的所有输入,从而确保序列化可以处理反序列化可以生成的所有内容。或者,Fuzz函数可以反序列化-序列化-反序列化-序列化,并检查第一次和第二次序列化的结果是否相等。又或者Fuzz函数可以将输入喂到两个不同的实现中(例如dumb和optimized),并检查输出是否相等。为了沟通应用程序级错误,Fuzz函数应该panic(OS.EXIT(1)也会工作,但是(panic message)应急消息包含更多信息)。注意,Fuzz函数不应该输出到stdout/stderr,它会减慢fuzzing的速度,而且无论如何也不会有人看到输出。例外情况是在panic之前打印有关错误的信息。

下面是image/png包的一个简单Fuzz函数示例:

package png

import (
	"bytes"
	"image/png"
)

func Fuzz(data []byte) int {
	png.Decode(bytes.NewReader(data))
	return 0
}

更有用的Fuzz函数如下:

func Fuzz(data []byte) int {
	img, err := png.Decode(bytes.NewReader(data))
	if err != nil {
		if img != nil {
			panic("img != nil on error")
		}
		return 0
	}
	var w bytes.Buffer
	err = png.Encode(&w, img)
	if err != nil {
		panic(err)
	}
	return 1
}

第二步是初始输入语料的收集。理想情况下,语料库中的文件尽可能小,并且尽可能多样化。您可以使用单元测试使用的输入或者生成它们。例如,对于一个图像解码包,您可以对几个bitmaps用不同的压缩级别(黑色、随机噪声、白色和少量非白色像素)进行编码,并将其用作初始语料库。Go-fuzz将消除重复并最小化输入。所以投入一千个输入是好的,多样性更重要。

把初始的语料库放到workdir/corpus目录中(在我们的例子中是examples/png/corpus)。go-fuzz将自己的输入添加到语料库目录。考虑将生成的输入提交到源代码管理系统,这将允许您重新启动go-fuzz而不会丢失以前的工作。

go-fuzz-corpus repository包含一系列测试函数示例和各种包的初始输入语料库。

下一步是获得go-fuzz

$ go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build

然后,下载语料库并使用必要的工具build测试程序:

$ go get -d github.com/dvyukov/go-fuzz-corpus
$ cd $GOPATH/src/github.com/dvyukov/go-fuzz-corpus
$ cd png
$ go-fuzz-build

这将生成png-fuzz.zip存档。

现在进行下一步:

$ go-fuzz

Go fuzz将在无限循环中生成和测试各种输入。Workdir用于存储当前语料库和崩溃等持久性数据,它允许fuzzer在重启后继续运行。发现的错误输入存储在workdir/crashers目录中;没有后缀的文件包含二进制输入,.quoted后缀的文件包含可以直接复制到再现程序或测试中的引用输入,.output后缀的文件包含此输入的测试输出。每隔几秒钟,将fuzz打印日志到表单的stderr:

2015/04/25 12:39:53 workers: 500, corpus: 186 (42s ago), crashers: 3,
     restarts: 1/8027, execs: 12009519 (121224/sec), cover: 2746, uptime: 1m39s

其中workers表示并行运行的测试数(使用-procs标志设置)。corpus是当前fuzzer发现的感兴趣输入的数量,括号中的时间表示最后一个感兴趣输入是什么时候被发现的。crashers是发现的错误数(查看workdir/crashers dir)。restarts是fuzzer重新启动测试进程的速率。速率应该接近1/10000(这是计划重启率);如果它大大高于1/10000,考虑修复导致频繁重启的已经发现的错误。execs是测试执行的总数,括号中的数字是测试执行的平均速度。cover是hashed coverage bitmap中设置的位数,如果该位数增加,fuzzer将发现新的代码行;位图的大小为64K;理想情况下cover值应小于~5000,否则fuzzer可能由于散列冲突而错过新的感兴趣输入。最后,uptime就是进程的正常运行时间。同样的信息也通过http提供(参见**-http**标志)。

Modules support

go-fuzz对go模块进行模糊测试有初步支持。go fuzz遵循标准GO111MODULE环境变量,可以将其设置为onoffauto

go-fuzz-build将在go.mod中添加对github.com/dvyukov/go-fuzzrequire。如果需要,可以在build完成后删除此项。

尚不支持使用模块进行vendor。vendor目录将被忽略,如果设置了GOFLAGS=-mod=vendor,go-fuzz将报告错误。

请注意,虽然使用模块来准备build,但最终instrumented build 仍在GOPATH模式下完成。对于大多数模块,这应该无关紧要。

libFuzzer support

go-fuzz build还可以生成一个归档文件,该文件可以与libFuzzer一起使用,代替go-fuzz(需要linux)。

Sample usage:

$ cd $GOPATH/src/github.com/dvyukov/go-fuzz-corpus/fmt
$ go-fuzz-build -libfuzzer  # produces fmt.a
$ clang -fsanitize=fuzzer fmt.a -o fmt.libfuzzer
$ ./fmt.libfuzzer

Continuous Fuzzing

正如单元测试一样,模糊化最好是连续进行的。

目前有2个服务提供基于go-fuzz的连续fuzzing:

Random Notes

go-fuzz-build使用gofuzz build标记build程序,这允许将fuzz函数实现直接放入测试包中,但使用**//+build gofuzz**指令将其从正常build中排除。

如果输入包含checksum,则在Fuzz函数中附加/更新checksum是有意义的。go-fuzz生成正确的checksum可能性很低,所以会导致大多数工作都是徒劳的。

go-fuzz可以利用几个机器。为此,请分别启动协调进程:

$ go-fuzz -workdir=examples/png -coordinator=127.0.0.1:8745

它将管理持久的语料库和崩溃,并协调工作进程的工作。然后运行一个或多个工作进程:

$ go-fuzz -bin=./png-fuzz.zip -worker=127.0.0.1:8745 -procs=10

External Articles

History rewrite

go fuzz repository history最近被重写为排除examples目录,以减少存储库的总大小和下载时间(请参见#88、#114和https://github.com/dvyukov/go fuzz corpus)。不幸的是,这意味着如果安装了以前的版本,go get-u命令将失败。请在再次运行go-get之前删除$GOPATH/github.com/dvyukov/go-fuzz。

Credits and technical details

Go fuzz fuzzing逻辑主要基于afl,如果您对技术细节感兴趣,请参阅AFL readme。AFL由Michal Zalewski编写和维护。go fuzz使用的一些突变是受Mateusz Jurczyk、Gynvael Coldwind和Felix Grébert所做工作的启发。

 类似资料: