- Go語言精進之路:從新手到高手的編程思想、方法和技巧(2)
- 白明
- 1474字
- 2022-01-04 17:42:24
45.4 使用go-fuzz建立模糊測試的示例
gocmpp(https://github.com/bigwhite/gocmpp)是一個中國移動cmpp短信協(xié)議庫的Go實現(xiàn),這里我們就用為該項目添加模糊測試作為示例。
gocmpp中的每種協(xié)議包都實現(xiàn)了Packer接口,其中的Unpack尤其適合做模糊測試。由于協(xié)議包眾多,我們在gocmpp下專門建立了fuzztest目錄,用于存放模糊測試的代碼,將各個協(xié)議包的模糊測試分到各個子目錄中:
github.com/bigwhite/gocmpp/fuzztest$tree . ├── fwd │ ├── corpus │ │ └── 0 │ ├── fuzz.go │ └── gen │ └── main.go └── submit ├── corpus │ ├── 0 ├── fuzz.go └── gen └── main.go
先說說每個模糊測試單元(比如fwd或submit)下的gen/main.go,這是一個用于生成初始語料的可執(zhí)行程序。以submit/gen/main.go為例:
// submit/gen/main.go package main import ( "github.com/dvyukov/go-fuzz/gen" ) func main() { data := []byte{ 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x00, ... 0x6d, 0x00, 0x69, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, } gen.Emit(data, nil, true) }
在這個main.go源文件中,我們借用submit包的單元測試中的數(shù)據(jù)作為模糊測試的初始語料數(shù)據(jù),通過go-fuzz提供的gen包將數(shù)據(jù)輸出到文件中:
$cd submit/gen $go run main.go -out ../corpus/ $ls -l ../corpus/ -rw-r--r-- 1 tony staff 181 12 7 22:00 0 ...
該程序在corpus目錄下生成了一個名為“0”的文件作為submit包模糊測試的初始語料。
接下來看看submit/fuzz.go:
// +build gofuzz package cmppfuzz import ( "github.com/bigwhite/gocmpp" ) func Fuzz(data []byte) int { p := &cmpp.Cmpp2SubmitReqPkt{} if err := p.Unpack(data); err != nil { return 0 } return 1 }
這是最為簡單的Fuzz函數(shù)實現(xiàn)了。根據(jù)作者對Fuzz的規(guī)約,F(xiàn)uzz的返回值是有重要含義的:
- 如果此次輸入的數(shù)據(jù)在某種程度上是很有意義的,go-fuzz會給予這類輸入更高的優(yōu)先級,F(xiàn)uzz應(yīng)該返回1;
- 如果明確這些輸入絕對不能放入corpus,那么讓Fuzz返回-1;
- 至于其他情況,則返回0。
接下來就該go-fuzz-build和go-fuzz登場了。這與前面的介紹差不多,我們先用go-fuzz-build構(gòu)建go-fuzz使用的帶有代碼覆蓋率統(tǒng)計樁代碼的二進制文件:
$cd submit $go-fuzz-build github.com/bigwhite/gocmpp/fuzztest/submit $ls cmppfuzz-fuzz.zip corpus/ fuzz.go gen/
然后在submit目錄下執(zhí)行g(shù)o-fuzz:
$go-fuzz -bin=./cmppfuzz-fuzz.zip -workdir=./ 2019/12/07 22:05:02 workers: 4, corpus: 1 (3s ago), crashers: 0, restarts: 1/0, execs: 0 (0/sec), cover: 0, uptime: 3s 2019/12/07 22:05:05 workers: 4, corpus: 3 (0s ago), crashers: 0, restarts: 1/0, execs: 0 (0/sec), cover: 32, uptime: 6s 2019/12/07 22:05:08 workers: 4, corpus: 7 (1s ago), crashers: 0, restarts: 1/5424, execs: 65098 (7231/sec), cover: 131, uptime: 9s 2019/12/07 22:05:11 workers: 4, corpus: 9 (0s ago), crashers: 0, restarts: 1/5424, execs: 65098 (5424/sec), cover: 146, uptime: 12s ... 2019/12/07 22:09:11 workers: 4, corpus: 9 (4m0s ago), crashers: 0, restarts: 1/9860, execs: 4033002 (16002/sec), cover: 146, uptime: 4m12s ^C2019/12/07 22:09:13 shutting down...
這個測試執(zhí)行非常耗CPU資源,一小會兒工夫,我的Mac Pro的風扇就開始呼呼轉(zhuǎn)動起來了。不過submit包的Unpack函數(shù)并未在這次短暫運行的模糊測試中發(fā)現(xiàn)問題,crashers后面的數(shù)值一直是0。
為了演示被測代碼在模糊測試中崩潰的情況,這里再舉一個例子(例子代碼改編自https://github.com/fuzzitdev/example-go)。在這個示例用,被測代碼如下:
// chapter8/sources/fuzz-test-demo/parse_complex.go package parser func ParseComplex(data [] byte) bool { if len(data) == 5 { if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'I' && data[5] == 'T' { return true } } return false }
為上述被測目標建立模糊測試:
// chapter8/sources/fuzz-test-demo/parse_complex_fuzz.go // +build gofuzz package parser func Fuzz(data []byte) int { ParseComplex(data) return 0 }
接下來按照套路,使用go-fuzz-build構(gòu)建go-fuzz使用的二進制zip文件并運行g(shù)o-fuzz:
$go-fuzz-build github.com/bigwhite/fuzz-test-demo $go-fuzz -bin=./parser-fuzz.zip -workdir=./ 2020/05/07 16:10:00 workers: 8, corpus: 6 (2s ago), crashers: 1, restarts: 1/0, execs: 0 (0/sec), cover: 0, uptime: 3s 2020/05/07 16:10:03 workers: 8, corpus: 6 (5s ago), crashers: 1, restarts: 1/0, execs: 0 (0/sec), cover: 10, uptime: 6s 2020/05/07 16:10:06 workers: 8, corpus: 6 (8s ago), crashers: 1, restarts: 1/5219, execs: 198330 (22034/sec), cover: 10, uptime: 9s 2020/05/07 16:10:09 workers: 8, corpus: 6 (11s ago), crashers: 1, restarts: 1/5051, execs: 383950 (31993/sec), cover: 10, uptime: 12s 2020/05/07 16:10:12 workers: 8, corpus: 6 (14s ago), crashers: 1, restarts: 1/5132, execs: 523514 (34898/sec), cover: 10, uptime: 15s 2020/05/07 16:10:15 workers: 8, corpus: 6 (17s ago), crashers: 1, restarts: 1/4930, execs: 631139 (35061/sec), cover: 10, uptime: 18s ^C2020/05/07 16:10:16 shutting down...
我們看到,在這次模糊測試執(zhí)行的輸出中,crashers的計數(shù)不再是0,而是1,這表明模糊測試引發(fā)了一次被測目標的崩潰。停掉模糊測試后,我們看到在測試執(zhí)行的工作目錄下出現(xiàn)了crashers和suppressions這兩個目錄:
$tree . ├── corpus │ ├── 1b7c3c5fec431a18fdebaa415d1f89a8f7a325bd-4 ... ├── crashers │ ├── df779ced6b712c5fca247e465de2de474d1d23b9 │ ├── df779ced6b712c5fca247e465de2de474d1d23b9.output │ └── df779ced6b712c5fca247e465de2de474d1d23b9.quoted ... ├── go.mod ├── parse_complex.go ├── parse_complex_fuzz.go ├── parser-fuzz.zip └── suppressions └── 4db970443bac2de13454771685ab603e779152b4
我們分別看看crashers和suppressions這兩個目錄下的內(nèi)容:
// suppressions目錄下的文件內(nèi)容 $ cat suppressions/4db970443bac2de13454771685ab603e779152b4 panic: runtime error: index out of range [5] with length 5 github.com/bigwhite/fuzz-test-demo.ParseComplex.func5 github.com/bigwhite/fuzz-test-demo.ParseComplex github.com/bigwhite/fuzz-test-demo.Fuzz go-fuzz-dep.Main main.main // crashers目錄下的文件內(nèi)容 $cat crashers/df779ced6b712c5fca247e465de2de474d1d23b9 FUZZI $cat crashers/df779ced6b712c5fca247e465de2de474d1d23b9.quoted "FUZZI" cat crashers/df779ced6b712c5fca247e465de2de474d1d23b9.output panic: runtime error: index out of range [5] with length 5 goroutine 1 [running]: github.com/bigwhite/fuzz-test-demo.ParseComplex.func5(...) chapter8/sources/fuzz-test-demo/parse_complex.go:5 github.com/bigwhite/fuzz-test-demo.ParseComplex(0x28a21000, 0x5, 0x5, 0x3bd-475a562627) chapter8/sources/fuzz-test-demo/parse_complex.go:5 +0x1be github.com/bigwhite/fuzz-test-demo.Fuzz(0x28a21000, 0x5, 0x5, 0x3) chapter8/sources/fuzz-test-demo/parse_complex_fuzz.go:6 +0x57 go-fuzz-dep.Main(0xc000104f70, 0x1, 0x1) go-fuzz-dep/main.go:36 +0x1ad main.main() github.com/bigwhite/fuzz-test-demo/go.fuzz.main/main.go:15 +0x52 exit status 2
從crashers/xxx.quoted中我們可以看到,引發(fā)此次崩潰的輸入數(shù)據(jù)為"FUZZI"這個字符串;從crashers/xxx.output或suppressions/4db970443bac2de13454771685ab603e779152b4我們可以看到,導(dǎo)致崩潰的直接原因為“下標越界”。這些信息足以讓我們快速定位到bug的位置:
data[5] == 'T'
接下來,我們可以修復(fù)該bug(可以將if len(data) == 5改為if len(data) == 6),并在該包的單元測試文件中添加一個針對該崩潰的用例,這里就不再贅述了。
- Effective C#:改善C#代碼的50個有效方法(原書第3版)
- Python測試開發(fā)入門與實踐
- 三維圖形化C++趣味編程
- 算法精粹:經(jīng)典計算機科學(xué)問題的Python實現(xiàn)
- 征服RIA
- Julia Cookbook
- 零基礎(chǔ)入門學(xué)習(xí)Python
- 移動界面(Web/App)Photoshop UI設(shè)計十全大補
- UVM實戰(zhàn)
- JavaCAPS基礎(chǔ)、應(yīng)用與案例
- ArcGIS for Desktop Cookbook
- QGIS 2 Cookbook
- Mudbox 2013 Cookbook
- 多媒體技術(shù)及應(yīng)用
- ASP.NET Core and Angular 2