优秀的编程知识分享平台

网站首页 > 技术文章 正文

golang 模糊测试fuzzing(golang testing)

nanyue 2024-11-02 12:18:35 技术文章 4 ℃

在golang 1.18中,新增了模糊测试特性fuzzing。 模糊测试(fuzz testing, fuzzing)是一种软件测试技术, 模糊测试是一种自动测试,它不断地操纵程序的输入来发现错误。Go fuzzing使用覆盖率指导来智能地遍历被模糊处理的代码,以发现bug并向用户报告BUG。由于模糊测试可以触及人类经常错过的边缘情况,因此它对于发现安全漏洞和漏洞尤其有价值。 下面是一个模糊测试的例子,突出显示了它的主要组件。

以下是模糊测试必须遵循的规则:

  • 模糊测试必须是一个名为FuzzXxx的函数,它只接受一个*testing.F、 并且没有返回值。
  • 模糊测试必须在*_test.go文件中才能运行。
  • 模糊目标必须是对(*testing.F)的方法调用。接受测试的引信。T作为第一个参数,然后是模糊参数。没有返回值。
  • 每个模糊测试必须只有一个模糊目标。
  • 所有种子语料库条目的类型都必须具有与模糊参数相同的类型,顺序相同。对 (*testing.F).Add的调用也是如此。fuzz测试的testdata/fuzz目录中的任何语料库文件。
  • 模糊参数只支持以下类型:
    • string, []byte
    • int, int8, int16, int32/rune, int64
    • uint, uint8/byte, uint16, uint32, uint64
    • float32, float64
    • bool

如何使用Fuzzing

1、创建一个main.go 内容为

package main

import (
    "fmt"
)

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

2、写一个单元测试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  -v
=== RUN   TestReverse
--- PASS: TestReverse (0.00s)
PASS
ok      demo    0.030s

接下来,您将把单元测试更改为模糊测试。

3、添加模糊测试

单元测试有局限性,即每个输入都必须由开发人员添加到测试中。模糊化的一个好处就是,它为代码提供了输入,并可能识别出您提出的测试用例没有达到的边缘用例。 在本节中,将把单元测试转换为模糊测试,这样就可以用更少的工作生成更多的输入! 请注意,您可以将单元测试、基准测试和模糊测试保存在同一个*_test.go文件中,但在本例中,您将把单元测试转换为模糊测试。

将reverse_test.go中的单元测试替换为以下模糊测试。

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

执行模糊测试

$ go test -v  -fuzz=Fuzz -fuzztime 10s
 === RUN   FuzzReverse
fuzz: elapsed: 0s, gathering baseline coverage: 0/13 completed
failure while testing seed corpus entry: FuzzReverse/31d6dcd7535b857a
fuzz: elapsed: 0s, gathering baseline coverage: 3/13 completed
--- FAIL: FuzzReverse (0.12s)
    --- FAIL: FuzzReverse (0.00s)
        reverse_test.go:20: Reverse produced invalid UTF-8 string "\xb3\xb3\xef"

=== NAME
FAIL
exit status 1
FAIL    demo    0.149s

另一个有用的选项是-fuzztime,它限制了模糊处理所花费的时间。例如,在下面的测试中指定-fuzztime 10s意味着,只要之前没有发生故障,测试将在10秒后默认退出。-fuzz 选项,指执行正则表达式匹配到的测试函数。请参阅cmd/go文档的这一部分,以查看其他测试选项。

注意

  • 模糊测试会消耗大量内存,并且可能会影响机器运行时的性能。另请注意,模糊引擎在运行时会将扩展测试覆盖率的值写入模糊缓存目录 $GOCACHE/fuzz。目前对可以写入模糊缓存的文件数量或总字节数没有限制,因此可能会占用大量存储空间(可能为数 GB)。
  • 模糊化也有一些局限性。在单元测试中,您可以预测Reverse函数的预期输出,并验证实际输出是否满足这些期望。 例如,在测试用例Reverse(“Hello,world”)中,单元测试将返回指定为“dlrow,olleH”。 当进行模糊处理时,您无法预测预期的输出,因为您无法控制输入。

links

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

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

https://blog.jetbrains.com/go/2022/12/14/understanding-fuzz-testing-in-go/

最近发表
标签列表