Go Fuzzing

Go 从 Go 1.18 开始在其标准工具链中支持 fuzzing。原生 Go fuzz 测试受到 OSS-Fuzz 支持

试用 使用 Go 进行 fuzzing 的教程

概述

Fuzzing 是一种自动测试,它持续操纵程序输入以查找错误。Go fuzzing 使用覆盖率引导来智能地遍历被 fuzz 的代码,以查找并向用户报告故障。由于它可以触及人类通常会遗漏的边缘情况,因此 fuzz 测试对于发现安全漏洞和漏洞特别有价值。

以下是一个 fuzz 测试 的示例,突出了它的主要组成部分。

Example code showing the overall fuzz test, with a fuzz target within
it. Before the fuzz target is a corpus addition with f.Add, and the parameters
of the fuzz target are highlighted as the fuzzing arguments. Example code showing the overall fuzz test, with a fuzz target within
it. Before the fuzz target is a corpus addition with f.Add, and the parameters
of the fuzz target are highlighted as the fuzzing arguments.

编写 fuzz 测试

要求

以下是 fuzz 测试必须遵循的规则。

建议

以下建议将帮助您充分利用 fuzzing。

运行 fuzz 测试

运行 fuzz 测试有两种模式:作为单元测试(默认 go test),或使用 fuzzing(go test -fuzz=FuzzTestName)。

默认情况下,fuzz 测试就像单元测试一样运行。每个 种子语料库条目 都会针对 fuzz 目标进行测试,在退出之前报告任何错误。

要启用 fuzzing,请使用 -fuzz 标志运行 go test,提供与单个 fuzz 测试匹配的正则表达式。默认情况下,该包中的所有其他测试将在 fuzzing 开始之前运行。这是为了确保 fuzzing 不会报告现有测试已经捕获的任何问题。

请注意,您需要决定运行 fuzzing 的时间。如果 fuzzing 没有找到任何错误,它可能会无限期地运行。将来会支持使用 OSS-Fuzz 等工具连续运行这些 fuzz 测试,请参见 问题 #50192

注意:fuzzing 应该在支持覆盖率检测的平台上运行(目前是 AMD64 和 ARM64),以便语料库在运行时可以有意义地增长,并且可以在 fuzzing 时覆盖更多代码。

命令行输出

在 fuzzing 过程中,fuzzing 引擎 生成新的输入并将它们运行到提供的 fuzz 目标。默认情况下,它会持续运行,直到找到 错误输入 或用户取消该进程(例如,使用 Ctrl^C)。

输出将类似于以下内容

~ go test -fuzz FuzzFoo
fuzz: elapsed: 0s, gathering baseline coverage: 0/192 completed
fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 325017 (108336/sec), new interesting: 11 (total: 202)
fuzz: elapsed: 6s, execs: 680218 (118402/sec), new interesting: 12 (total: 203)
fuzz: elapsed: 9s, execs: 1039901 (119895/sec), new interesting: 19 (total: 210)
fuzz: elapsed: 12s, execs: 1386684 (115594/sec), new interesting: 21 (total: 212)
PASS
ok      foo 12.692s

前几行表明在 fuzzing 开始之前收集了“基线覆盖率”。

为了收集基线覆盖率,fuzzing 引擎会执行 种子语料库生成的语料库,以确保没有发生错误,并了解现有语料库已经提供的代码覆盖率。

接下来的几行提供了对正在进行的 fuzzing 执行的洞察

要成为“有趣”的输入,它必须扩展现有生成的语料库无法触及的代码覆盖率。通常情况下,新的有趣输入的数量在开始时会迅速增长,并最终逐渐减慢,偶尔会随着发现新的分支而突然增加。

您应该预计随着语料库中的输入开始覆盖更多代码行,“new interesting” 的数量会随着时间的推移而逐渐减少,如果 fuzzing 引擎发现了新的代码路径,偶尔也会突然增加。

错误输入

fuzzing 过程中可能会出现错误,原因如下

如果发生错误,fuzzing 引擎将尝试将输入最小化到仍然会产生错误的最小可能值和最容易理解的值。要配置这一点,请参见 自定义设置 部分。

最小化完成后,错误消息将被记录,输出将以类似于以下内容结尾

    Failing input written to testdata/fuzz/FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
    To re-run:
    go test -run=FuzzFoo/a878c3134fe0404d44eb1e662e5d8d4a24beb05c3d68354903670ff65513ff49
FAIL
exit status 1
FAIL    foo 0.839s

fuzzing 引擎将此 错误输入 写入该 fuzz 测试的种子语料库中,现在它将默认情况下使用 go test 运行,作为回归测试,一旦错误被修复。

下一步,您需要诊断问题,修复错误,通过重新运行 go test 验证修复,并提交包含新 testdata 文件的补丁作为回归测试。

自定义设置

默认的 go 命令设置应该适用于大多数 fuzzing 用例。因此,通常情况下,在命令行上执行 fuzzing 应该看起来像这样

$ go test -fuzz={FuzzTestName}

但是,go 命令在运行 fuzzing 时提供了一些设置。这些在 cmd/go 包文档 中有记录。

重点介绍几个

语料库文件格式

语料库文件以特殊格式编码。这对于 种子语料库生成的语料库 来说是相同的格式。

以下是一个语料库文件的示例

go test fuzz v1
[]byte("hello\\xbd\\xb2=\\xbc ⌘")
int64(572293)

第一行用于告知 fuzzing 引擎文件的编码版本。虽然目前没有计划未来版本的编码格式,但设计必须支持这种可能性。

以下的每一行都是构成语料库条目的值,如果需要,可以将其直接复制到 Go 代码中。

在上面的示例中,我们有一个 []byte 后面跟着一个 int64。这些类型必须与 fuzzing 参数完全匹配,并且顺序相同。针对这些类型的 fuzz 目标将如下所示

f.Fuzz(func(*testing.T, []byte, int64) {})

指定您自己的种子语料库值的 easiest 方法是使用 (*testing.F).Add 方法。在上面的示例中,这将如下所示

f.Add([]byte("hello\\xbd\\xb2=\\xbc ⌘"), int64(572293))

但是,你可能拥有大型二进制文件,你可能更倾向于不将它们作为代码复制到你的测试中,而是将它们保留为测试数据/fuzz/{FuzzTestName} 目录中的单个种子语料库条目。 file2fuzz 工具位于 golang.org/x/tools/cmd/file2fuzz,可用于将这些二进制文件转换为针对 []byte 编码的语料库文件。

要使用此工具

$ go install golang.org/x/tools/cmd/file2fuzz@latest
$ file2fuzz -h

资源

术语表

语料库条目: 语料库中的一个输入,可在模糊测试时使用。 这可以是格式特殊的 文件,或者是对 (*testing.F).Add 的调用。

覆盖率引导: 一种模糊测试方法,它使用代码覆盖率的扩展来确定哪些语料库条目值得保留以供将来使用。

失败输入: 失败输入是当针对 模糊测试目标 运行时会导致错误或恐慌的语料库条目。

模糊测试目标: 模糊测试的函数,该函数在模糊测试期间针对语料库条目和生成的 值执行。 它通过将函数传递给 (*testing.F).Fuzz 提供给模糊测试。

模糊测试: 测试文件中形式为 func FuzzXxx(*testing.F) 的函数,可用于模糊测试。

模糊测试: 一种自动测试类型,它持续地对程序输入进行操作,以查找诸如错误或 漏洞 等问题,代码可能容易受到这些问题的攻击。

模糊测试参数: 将传递给模糊测试目标的类型,以及由 变异器 进行修改的类型。

模糊测试引擎: 一种管理模糊测试的工具,包括维护语料库、调用变异器、识别新的覆盖率以及报告失败。

生成语料库: 模糊测试引擎在模糊测试期间随着时间的推移维护的语料库,用于跟踪进度。 它存储在 $GOCACHE/fuzz 中。 这些条目仅在模糊测试期间使用。

变异器: 模糊测试期间使用的工具,在将语料库条目传递给模糊测试目标之前对其进行随机操作。

包: 同一目录中的一组源文件,它们一起编译。 请参阅 Go 语言规范中的 包部分

种子语料库: 模糊测试的用户提供的语料库,可用于指导模糊测试引擎。 它由模糊测试中 f.Add 调用提供的语料库条目和包中测试数据/fuzz/{FuzzTestName} 目录中的文件组成。 这些条目默认情况下通过 go test 运行,无论是否进行模糊测试。

测试文件: 格式为 xxx_test.go 的文件,其中可能包含测试、基准测试、示例和模糊测试。

漏洞: 代码中一个安全敏感的弱点,攻击者可以利用它。

反馈

如果您遇到任何问题或有功能想法,请 提交问题

有关功能的讨论和一般反馈,您也可以参加 Gophers Slack 中的 #fuzzing 频道