官术网_书友最值得收藏!

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),并在該包的單元測試文件中添加一個針對該崩潰的用例,這里就不再贅述了。

主站蜘蛛池模板: 司法| 桐乡市| 镇赉县| 宜兰市| 三门县| 汉寿县| 丰县| 肥西县| 石柱| 营山县| 东兰县| 桃源县| 长垣县| 太白县| 阜阳市| 堆龙德庆县| 卢湾区| 黎川县| 赫章县| 内黄县| 南城县| 荔浦县| 江门市| 巴南区| 礼泉县| 克东县| 盐源县| 砀山县| 青岛市| 无棣县| 天门市| 和静县| 望江县| 双辽市| 玉溪市| 贵溪市| 邯郸市| 乐山市| 本溪市| 八宿县| 永济市|