?強大的模糊測試工具 go-fuzz
推薦 go-fuzz 的背景
我們在日常開發中經常會編寫測試和對應的測試用例,大家是否常常會有以下疑惑:
- 現有的測試用例是否完全覆蓋了各種邊界場景?會不會有意料之外的 case?
- 代碼測試覆蓋率都達到 100% 了,代碼上線時為啥還會戰戰兢兢?
- 寫測試用例太費心費力了,有沒有一款能自動生成測試用例的工具?
這次要推薦給大家的 go-fuzz 也許能讓你的工程魯棒性再上一個臺階,并或多或少緩解你的擔憂。
go-fuzz(https://github.com/dvyukov/go-fuzz)是 Dmitry Vyukov 大神早在 go1.5 時代開源(Apache License 2.0 開源許可)的一款 golang 模糊測試工具,為解析復雜輸入(文本或二進制)的系統提供了強大的魯棒性驗證手段。迄今為止,go-fuzz 已經為 go 語言(你沒看錯,就是 golang 自身)和一些三方庫檢測出了幾百個缺陷,可謂居功至偉!
go-fuzz 與模糊測試
維基百科對模糊測試的解釋如下:
模糊測試 (fuzz testing, fuzzing)是一種軟件測試技術。其核心思想是將自動或半自動生成的隨機數據輸入到一個程序中,并監視程序異常,如崩潰,斷言(assertion)失敗,以發現可能的程序錯誤,比如內存泄漏。模糊測試常常用于檢測軟件或計算機系統的安全漏洞。
go-fuzz 自動生成的測試用例可不是簡單的隨機,它強依賴于 afl(American Fuzzy Lop),在執行時會根據程序現有的用例和用例執行情況按照一定的算法規則進行迭代修改,從而無限“裂變”生成新測試。
目前 gitlab.com 和 fuzzbuzz.io 上均有基于 go-fuzz 的 ci 集成。
go-fuzz 應用舉例
下面我們就以 fuzzbuzz.io 上的小例子來看 go-fuzz 如何使用。如下代碼中有一處比較隱晦的 bug,當輸入的 Data 剛好是 FUZ 時會觸發訪問越界:
package tutorial
// BrokenMethod has a bug - it will try to read the 4th
// index of Data even when it only has a length of 3.
func BrokenMethod(Data string) bool {
return len(Data) >= 3 &&
Data[0] == 'F' &&
Data[1] == 'U' &&
Data[2] == 'Z' &&
Data[3] == 'Z'
}
接下來我們嘗試使用 go-fuzz 對漏洞進行探測
Step 0: 安裝 go-fuzz-build 和 go-fuzz
go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest
別忘了把 $GOPATH/bin 添加到 PATH 中
Step 1: 編寫測試函數
在代碼中添加 method_fuzz.go,注意 // +build gofuzz 是必須添加的,接下來的構建步驟會對其進行識別。
// +build gofuzz
package tutorial
func Fuzz(data []byte) int {
BrokenMethod(string(data))
return 0
}
Fuzz函數的返回碼目前有 3 個可選值:返回1表示當前的輸入權重增加,返回-1表示當前的輸入不添加進語料庫,否則返回0。
Step 2: 設計幾個初始語料
我們添加 F 和 FU 作為 BrokenMethod 的兩個測試用例。當然,如果你的代碼中有一些已經設計好的用例,也可以直接復制到 workdir/corpus 下。
mkdir -p workdir/corpus echo -n "F" >workdir/corpus/1 echo -n "FU" >workdir/corpus/2
添加初始語料不是必須的,但是 go-fuzz 作者建議初始語料越豐富越好,這對后續的模糊測試執行很有幫助!Step 3: go-fuzz-build 生成測試工程
go get -d github.com/dvyukov/go-fuzz-corpus go-fuzz-build
這一步可能需要花一些時間,這跟工程的復雜度有關系。執行成功后,會在當前目錄里看到一個 tutorial-fuzz.zip 的壓縮包。
go-fuzz是 go1.5 時期的老家伙了,當前對 go module 的支持還處于早期階段。構建測試前執行go get -d github.com/dvyukov/go-fuzz-corpus會在 go.mod 里添加一行并不需要的依賴,模糊測試執行完畢后使用 go mod tidy 即可恢復。
Step 4: go-fuzz 執行模糊測試
go-fuzz -bin=tutorial-fuzz.zip -workdir=workdir
這時我們看到控制臺有如下輸出:
2021/05/16 13:56:45 workers: 4, corpus: 4 (2s ago), crashers: 1, restarts: 1/0, execs: 0 (0/sec), cover: 0, uptime: 3s 2021/05/16 13:56:48 workers: 4, corpus: 4 (5s ago), crashers: 1, restarts: 1/0, execs: 0 (0/sec), cover: 6, uptime: 6s 2021/05/16 13:56:51 workers: 4, corpus: 4 (8s ago), crashers: 1, restarts: 1/408, execs: 48969 (5440/sec), cover: 6, uptime: 9s ...
go-fuzz 執行測試時不會自動終止,當我們發現 crashers 字段的值不為 0 時(有用例觸發了程序異常),就可以終止測試并查看測試結果了,導致程序異常的用例會存放在 workdir/crashers/ 目錄中Step 5: 分析測試結果
$ tree workdir/crashers/ workdir/crashers ├── 0eb8e4ed029b774d80f2b66408203801cb982a60 ├── 0eb8e4ed029b774d80f2b66408203801cb982a60.output └── 0eb8e4ed029b774d80f2b66408203801cb982a60.quoted
可見,workdir/crashers 中多出了 3 個文件,它們的文件名均為輸入用例的 sha1sum 值。
- 不帶后綴的文件存放用例的原始輸入
- 后綴
.quoted的文件存放字符串形式的用例輸入(方便貼入代碼直接進行調試,設計太友好了) - 后綴為
.output的文件存放異常時的錯誤輸出
$ cat workdir/crashers/0eb8e4ed029b774d80f2b66408203801cb982a60.quoted "FUZ" $ cat workdir/crashers/0eb8e4ed029b774d80f2b66408203801cb982a60.output panic: runtime error: index out of range [3] with length 3 goroutine 1 [running]: demo.BrokenMethod.func4(...) /Users/blanet/repos/tmp/tutorial-go/method.go:9 demo.BrokenMethod(0xc000092e80, 0x3, 0x3) /Users/blanet/repos/tmp/tutorial-go/method.go:10 +0x11d demo.Fuzz(0x4810000, 0x3, 0x3, 0x3) /Users/blanet/repos/tmp/tutorial-go/fuzz.go:5 +0x6f go-fuzz-dep.Main(0xc000092f70, 0x1, 0x1) go-fuzz-dep/main.go:36 +0x1b8 main.main() demo/go.fuzz.main/main.go:15 +0x52 exit status 2
至此,我們找到了程序中的漏洞以及復現漏洞的用例,稍加調試問題就迎刃而解了!漏洞修復后,我們還可以為找到的 bad case 設計新的單元測試,進一步提升代碼質量。
總結
使用 go-fuzz 可以為程序集成模糊測試,對于檢測復雜輸入系統的魯棒性、篩查各種深水區 panic 的場景非常有幫助。大家趕快試用吧!