简洁

本教程介绍了 Go 中模糊测试的基础知识。

通过模糊测试,随机数据会针对您的测试运行,以尝试找到漏洞或导致崩溃的输入。 可以通过模糊测试发现的漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击。

在本教程中,您将为一个简单的函数编写模糊测试,运行 go 命令,并调试和修复代码中的问题。

测试

创建文件

  [plaintext]
1
cd D:\_go\06-fuzz

初始化 mod

  [plaintext]
1
2
3
go mod init example/fuzz go: creating new go.mod: module example/fuzz

编写代码

  • main.go
  [go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main import "fmt" func Reverse(s string) string { b := []byte(s) for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 { b[i], b[j] = b[j], b[i] } return string(b) } func main() { input := "The quick brown fox jumped over the lazy dog" rev := Reverse(input) doubleRev := Reverse(rev) fmt.Printf("original: %q\n", input) fmt.Printf("reversed: %q\n", rev) fmt.Printf("reversed again: %q\n", doubleRev) }

运行

  [plaintext]
1
2
3
4
5
go run . original: "The quick brown fox jumped over the lazy dog" reversed: "god yzal eht revo depmuj xof nworb kciuq ehT" reversed again: "The quick brown fox jumped over the lazy dog"

添加单元测试

新建文件 reverse_test.go,内容如下:

  [go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main import ( "testing" ) func TestReverse(t *testing.T) { testcases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {" ", " "}, {"!12345", "54321!"}, } for _, tc := range testcases { rev := Reverse(tc.in) if rev != tc.want { t.Errorf("Reverse: %q, want %q", rev, tc.want) } } }

执行测试

  [plaintext]
1
2
3
4
$ go test PASS ok example/fuzz 0.927s

添加 FUZZ

说明

单元测试有局限性,即每个输入都必须由开发人员添加到测试中。

模糊测试的好处之一是它可以为您的代码提供输入,并且可以识别您提出的测试用例未达到的边缘情况。

在本节中,您将把单元测试转换为模糊测试,以便您可以用更少的工作生成更多的输入!

请注意,您可以将单元测试、基准测试和模糊测试保留在同一个 *_test.go 文件中,但对于本示例,您将把单元测试转换为模糊测试。

代码

我们把测试代码的内容改成下面的

  [go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main import ( "testing" "unicode/utf8" ) func FuzzReverse(f *testing.F) { testcases := []string{"Hello, world", " ", "!12345"} for _, tc := range testcases { f.Add(tc) // Use f.Add to provide a seed corpus } f.Fuzz(func(t *testing.T, orig string) { rev := Reverse(orig) doubleRev := Reverse(rev) if orig != doubleRev { t.Errorf("Before: %q, after: %q", orig, doubleRev) } if utf8.ValidString(orig) && !utf8.ValidString(rev) { t.Errorf("Reverse produced invalid UTF-8 string %q", rev) } }) }

测试

1) 基本用例

运行模糊测试而不对其进行模糊测试,以确保种子输入通过。

  [plaintext]
1
2
3
$ go test PASS ok example/fuzz 1.306s

2) 运行 FuzzReverse 进行模糊测试,以查看任何随机生成的字符串输入是否会导致失败。

这是使用 go test 执行的,并带有一个新标志 -fuzz,设置为参数 Fuzz。 复制下面的命令。

  [plaintext]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ go test -fuzz=Fuzz fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 16 workers fuzz: minimizing 29-byte failing input file fuzz: elapsed: 0s, minimizing --- FAIL: FuzzReverse (0.21s) --- FAIL: FuzzReverse (0.00s) reverse_test.go:20: Reverse produced invalid UTF-8 string "\x80\xd9" Failing input written to testdata\fuzz\FuzzReverse\d4cc6f6ab27db823 To re-run: go test -run=FuzzReverse/d4cc6f6ab27db823 FAIL exit status 1 FAIL example/fuzz 1.812s

3)

参考资料

https://go.dev/security/fuzz/#glossary

https://go.dev/doc/tutorial/fuzz