简洁

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

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

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

测试

创建文件

cd D:\_go\06-fuzz

初始化 mod

go mod init example/fuzz

go: creating new go.mod: module example/fuzz

编写代码

  • main.go
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)
}

运行

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,内容如下:

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)
        }
    }
}

执行测试

$ go test

PASS
ok      example/fuzz    0.927s

添加 FUZZ

说明

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

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

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

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

代码

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

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) 基本用例

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

$ go test
PASS
ok      example/fuzz    1.306s

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

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

$ 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