亲测体验Go语言模糊测试

共 6194字,需浏览 13分钟

 ·

2024-03-26 07:00


何为 模糊测试 (Fuzz Testing)?


模糊测试是一种自动化的软件测试技术,它通过向程序提供无效、意外或随机的数据作为输入来检测软件中的错误、漏洞或失败。这种测试方法的目的是找到程序处理意外或异常输入时可能会崩溃或表现出异常行为的地方。

模糊测试已成为软件开发和安全领域的一个重要组成部分。对很多基础软件,金融类软件,安全圈子的各位"师傅"们,可能在跑着若干Fuzz Testing以寻找漏洞。


作用


  1. 发现安全漏洞:如缓冲区溢出、内存泄漏、注入攻击等,这些通常在正常的测试用例中可能被忽略。

  2. 增强软件稳定性:帮助开发者识别和修复导致程序崩溃或行为异常的代码。

  3. 验证输入验证:确保程序能够适当地处理不合规格的输入。

  4. 自动化测试:模糊测试可以自动进行,覆盖更广泛的测试用例。


步骤


模糊测试通常包括以下步骤:

  1. 生成测试用例:使用随机化或一些算法生成大量不同的输入数据。

  2. 执行程序:将这些测试用例作为输入提供给待测试的程序。

  3. 监控程序行为:检测程序崩溃、功能失败、代码异常执行等问题。

  4. 分析结果:如果程序在处理某个输入时失败,分析其原因并报告。


语料库来源


语料库是模糊测试中使用的一组数据,用于生成测试用例。通常来自以下来源:

  1. 现有的测试用例:利用已有的测试数据作为基础,通过变异生成新的测试用例。

  2. 实际数据样本:从生产环境或实际应用场景中提取的数据,以确保测试用例接近真实世界的情况。

  3. 开源数据集:特定领域的开源数据集,例如网络协议、文件格式等。

  4. 随机生成的数据:完全随机或遵循特定模式和规则生成的数据。

  5. 专门的工具和库:一些工具和库专门设计用来生成用于模糊测试的语料库,例如 AFL(American Fuzzy Lop)和 LibFuzzer。


使用模糊测试的注意事项


  • 资源消耗:模糊测试可能需要大量计算资源和时间。

  • 误报,即假阳性:可能会产生大量的假阳性结果,报告了非问题或不重要的问题。

  • 测试覆盖范围:虽然可以发现很多问题,但不能保证完全的代码覆盖率,因此应与其他测试方法结合使用。


Go语言模糊测试


Go Fuzzing 由发布于2022年3月份的Go 1.18版本引入,迄今已近两年。但感觉总体关注度不太高,多半是因为1.18中众所期待的泛型,掩过了其风头。

类似Rob Pike曾在泛型发布前夕,提issue建议放慢节奏。印象里,大佬也曾对Go Fuzzing "毒舌"过: 这东西有何意义? 就是找一百万只猴子,在键盘前随机敲打?

事实上,包括单元测试在内的诸多测试,都可以认为是白盒测试---我知道逻辑,构造输入并验证预期结果和实际输出是否一致. 但难免有很多犄角旮旯的边角情况考虑不到,模糊测试恰好弥补了这一点,可以认为是一种黑盒测试.

关于Go Fuzzing,,绕不开dvyukov[1],他也是Go调度器的开发者,Google员工(但不在Go Team). 其最早提了加入fuzz test的提案,自己也有一个很有名的项目 dvyukov/go-fuzz[2],,并用此工具找出了标准库上百个错误[3]..

github.com/google下面也有一个类似的项目 github.com/google/gofuzz[4], 不过已经很久没维护了~


另外, github.com/google/syzkaller[5]  这个项目也是他重度参与的:

syzkaller is an unsupervised coverage-guided kernel fuzzer , 是一个针对部分操作系统内核的 fuzzer工具...这个在安全圈使用很多,用来挖内核漏洞...

内核fuzz工具Syzkaller使用方法的简单介绍 [6]


2022年之前的文章,基本都是讲dvyukov写的这个第三方库go-fuzz怎么用,而不是go官方的(因为当时还没有集成进去)

关于Go的模糊测试,更多可以参考TonyBai老师的这篇文章:

Go 1.18新特性前瞻:原生支持Fuzzing测试 [7]

以及

你需要了解的 Go 中的模糊测试 | Linux 中国


亲测体验


先写一个Multiply函数,返回 a 和 b 的乘积,但故意有一个 bug

main.go:

      
      package main

import "fmt"

// Multiply 返回 a 和 b 的乘积
func Multiply(a, b int) int {
    // 故意引入的 bug: 当 a 和 b 都是负数时,返回错误的结果
    if a < 0 && b < 0 {
        return a + b
    }
    return a * b
}

func main() {
    fmt.Println("Multiply 3 and 4:", Multiply(34))
}

再写一个单元测试,但这个测试无法捕捉到上述的 bug:

main_test.go:

      
      package main

import "testing"

func TestMultiply(t *testing.T) {
    testCases := []struct {
        a, b, expected int
    }{
        {3412},
        {-34-12},
        {000},
    }

    for _, tc := range testCases {
        if res := Multiply(tc.a, tc.b); res != tc.expected {
            t.Errorf("Multiply(%d, %d) = %d; expected %d", tc.a, tc.b, res, tc.expected)
        }
    }
}

单测属于白盒测试,如果编写者没有考虑到 a 和 b 都是负数的情况,则这个单元测试将会通过,无法捕捉到这个 bug。

ea51f0fac20968f313582f2bf09aabb7.webp

再编写一个模糊测试来捕捉单测未发现的 Bug

模糊测试是一种自动化测试技术,用于生成随机输入数据来测试程序。在 Go 中,可以使用 testing 包提供的 Fuzz 功能来实现模糊测试。这需要 Go 1.18 或更高版本。

      
      package main

import "testing"


func FuzzMultiply(f *testing.F) {
 testCases := []struct {
  a, b int
 }{
  {34},
  {-34},
  {00},
 }

 for _, tc := range testCases {
  f.Add(tc.a, tc.b) // 添加已知的测试用例(这段内容也可以去掉)
 }

 f.Fuzz(func(t *testing.T, a, b int) {
  // 这里,a 和 b 是随机生成的
  expected := a * b
  if res := Multiply(a, b); res != expected {
   t.Errorf("Multiply(%d, %d) = %d; expected %d", a, b, res, expected)
  }
 })
}

这段模糊测试将生成随机的 ab 值,并用其来测试 Multiply 函数。如果 Multiply 函数的实现有 bug,这个模糊测试很可能会揭露


通过以下命令 运行模糊测试:

      
      go test -fuzz=Fuzz

模糊测试将不断生成新的随机输入,并很快揭示故意引入的 bug:

9c58c062cf9c0945a1a5dc37771a0af4.webp

输出信息会显示FAIL,并输出导致错误的用例.

还会生成一个testdata目录,其中会生成一个fuzz/FuzzMultiply目录,里面有一个随机文件,内容也是导致错误的用例.


另外, go test -fuzz会先进行普通TestXxx的用例执行,之后才会执行FuzzXxx。

fuzz testing默认会一直执行下去,直到遇到crash。

比如修复Multiply中故意引入的这个bug,再执行,就会一直执行下去

5d23d1c3a26d329193330547ec562b28.webp

如果要限制fuzz testing的执行时间,可以使用-fuzztime,如下面的命令只允许fuzz testing执行10s:

go test -fuzz=Fuzz -fuzztime 10s

d40ba5d268a5f529fccf495c525c4d7a.webp

go test -fuzz=Fuzz会运行项目目录下所有以 Fuzz 开头的 Fuzz 测试函数.

如果想精确指定运行某个 Fuzz 测试函数,如此处的FuzzMultiply, 可以使用

go test -v ./ -run Fuzz.+ -fuzz=FuzzMultiply


当然,真实项目中的bug,不会如此低级,逻辑会更复杂,隐藏得更深,模糊测试花费的时间也会更多.

可以肯定,此时此刻,针对某些知名的广泛使用的项目,正以月,以年为单位,旷日持久得在很多机器上跑着模糊测试,以期能捕获很难直观发现的 bug。


参考资料 [1]

dvyukov: https://github.com/dvyukov

[2]

dvyukov/go-fuzz: https://github.com/dvyukov/go-fuzz

[3]

标准库上百个错误: https://github.com/dvyukov/go-fuzz#trophies

[4]

github.com/google/gofuzz: https://github.com/google/gofuzz

[5]

github.com/google/syzkaller: https://github.com/google/syzkaller

[6]

内核fuzz工具Syzkaller使用方法的简单介绍: https://www.bilibili.com/video/BV1gL4y1j7Cs

[7]

Go 1.18新特性前瞻:原生支持Fuzzing测试: https://tonybai.com/2021/12/01/first-class-fuzzing-in-go-1-18/

推荐阅读:

我是如何实现Go性能5倍提升的?

「GoCN酷Go推荐」我用go写了魔兽世界登录器?

Go区不大,创造神话,科目三杀进来了

Go 1.22新特性前瞻

这些流行的K8S工具,你都用上了吗


想要了解Go更多内容,欢迎扫描下方👇关注公众号, 回复关键词 [实战群]   ,就有机会进群和我们进行交流



分享、在看与点赞Go  9f907a5b15e672119a5ce4c3e188d424.webp
浏览 28
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报