強大的模糊測試工具 go-fuzz

bupt_xingxin發表於2021-05-16

推薦 go-fuzz 的背景

我們在日常開發中經常會編寫測試和對應的測試用例,大家是否常常會有以下疑惑:

  • 現有的測試用例是否完全覆蓋了各種邊界場景?會不會有意料之外的 case?
  • 程式碼測試覆蓋率都達到 100% 了,程式碼上線時為啥還會戰戰兢兢?
  • 寫測試用例太費心費力了,有沒有一款能自動生成測試用例的工具?

這次要推薦給大家的 go-fuzz 也許能讓你的工程魯棒性再上一個臺階,並或多或少緩解你的擔憂。

go-fuzzhttps://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 自動生成的測試用例可不是簡單的隨機,它強依賴於 aflAmerican Fuzzy Lop),在執行時會根據程式現有的用例和用例執行情況按照一定的演算法規則進行迭代修改,從而無限 “裂變” 生成新測試。

目前 gitlab.comfuzzbuzz.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 對漏洞進行探測

Step0: 安裝 go-fuzz-buildgo-fuzz

go get -u github.com/dvyukov/go-fuzz/go-fuzz@latest github.com/dvyukov/go-fuzz/go-fuzz-build@latest

別忘了把 $GOPATH/bin 新增到 PATH 中

Step1: 編寫測試函式

在程式碼中新增 method_fuzz.go,注意 // +build gofuzz 是必須新增的,接下來的構建步驟會對其進行識別。

// +build gofuzz
package tutorial

func Fuzz(data []byte) int {
  BrokenMethod(string(data))
  return 0
}

Fuzz 函式的返回碼目前有 3 個可選值:返回 1 表示當前的輸入權重增加,返回 -1 表示當前的輸入不新增進語料庫,否則返回 0

Step2: 設計幾個初始語料

我們新增 FFU 作為 BrokenMethod 的兩個測試用例。當然,如果你的程式碼中有一些已經設計好的用例,也可以直接複製到 workdir/corpus 下。

mkdir -p workdir/corpus
echo -n "F"  >workdir/corpus/1
echo -n "FU" >workdir/corpus/2

新增初始語料不是必須的,但是 go-fuzz 作者建議初始語料越豐富越好,這對後續的模糊測試執行很有幫助!

Step3: 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 即可恢復。

Step4: 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/ 目錄中

Step5: 分析測試結果

$ 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 的場景非常有幫助。大家趕快試用吧!

參考資料


歡迎加入 GOLANG 中國社群:https://gocn.vip

更多原創文章乾貨分享,請關注公眾號
  • 強大的模糊測試工具 go-fuzz
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章