2021/09/21

Recent entries from same category

  1. Go 言語プログラミングエッセンスという本を書きました。
  2. errors.Join が入った。
  3. unsafe.StringData、unsafe.String、unsafe.SliceData が入った。
  4. Re: Go言語で画像ファイルか確認してみる
  5. net/url に JoinPath が入った。

みなさん Fuzz testing ってご存じでしょうか。

人間が作る物は必ずといっていいほどバグが存在します。そしてそのコードをテストする人間も必ずバグを見逃します。

想定していなかった境界値テスト等、人間には先入観という物があり、それが邪魔をして簡単にバグを見逃します。昨今、この様な誰も気付かなかったバグの隙間を突く様な脆弱性が沢山見つかっています。

物によっては重大インシデントに発展する物まであります。

こういった人間では想定できない様なバグを見付けてくれるのが Fuzz testing です。Fuzz testing を実施する事で、ソフトウェアは頑丈になり安全にもなりえます。

本日、Go の master ブランチに Fuzz testing の機能が入りました。

[dev.fuzz] Merge remote-tracking branch 'origin/dev.fuzz' into merge-… · golang/go@6e81f78 · GitHub

+1 −0 api/except.txt +36 −0 api/next.txt +63 −15 src/cmd/go/alldocs.go +10 −0 src/cmd/go/internal/ca...

https://github.com/golang/go/commit/6e81f78c0f1653ea140e6c8d008700ddad1fa0a5
これまで Go でテストを書いた事がある皆さんであれば、Go のテストは xxx_test.go というファイルに、 Test から始まり引数に *testing.T を持つ関数を定義される事はご存じだと思います。今回入る Fuzz はこれに似ており先頭が Fuzz で引数に *testing.F を持つ関数が実行対象となります。 package foo_test

import "testing"

func FuzzFoo(f *testing.F) {
    f.Log("all good here")
    f.Fuzz(func(*testing.T, []byte) {})
}

例えば以下の関数があったとしましょう。b のゼロ長さチェックはしていますが、2バイト必要かどうかのテストが抜けています。

func doSomething(s stringbool {
    if len(s) > 0 && s[0] == 0xff && s[1] == 0xff {
        return true
    }
    return false
}

以下の様に、f.Fuzz の第二引数に受け取りたい fuzz 値を得られる様にして実装します。

package foo_test

import "testing"

func doSomething(s stringbool {
    if len(s) > 0 && s[0] == 0xff && s[1] == 0xff {
        return true
    }
    return false
}

func FuzzFoo(f *testing.F) {
    f.Log("all good here")
    f.Fuzz(func(f *testing.T, b []byte) {
        doSomething(string(b))
    })
}

コマンドラインから以下の様に実行します。

$ go test -fuzz=.

すると以下の様に落ちる箇所を見付けてくれます。

gathering baseline coverage, elapsed: 0s, workers: 4, left: 2
fuzz: found a 31-byte crash input; minimizing...
FAIL
fuzz: elapsed: 1s, execs: 9807 (14334/sec), workers: 4, interesting: 0
--- FAIL: FuzzFoo (0.69s)
    foo_test.go:13: all good here
        foo_test.go:13: all good here
        --- FAIL: FuzzFoo (0.00s)
            testing.go:1244: panic: runtime error: index out of range [1] with length 1
                goroutine 3187 [running]:
                runtime/debug.Stack()
                    C:/go/src/runtime/debug/stack.go:24 +0x90
                testing.tRunner.func1()
                    C:/go/src/testing/testing.go:1244 +0x545
                panic({0x65c2e0, 0xc000014138})
                    C:/go/src/runtime/panic.go:814 +0x207
                github.com/mattn/go-fuzz-example_test.doSomething(...)
                    C:/Users/mattn/go/src/github.com/mattn/go-fuzz-example/foo_test.go:6
                github.com/mattn/go-fuzz-example_test.FuzzFoo.func1(0x0, {0xc000180000, 0x0, 0x4f37d9})
                    C:/Users/mattn/go/src/github.com/mattn/go-fuzz-example/foo_test.go:15 +0xfb
                reflect.Value.call({0x63c8e0, 0x67a178, 0x30}, {0x66a8b0, 0x4}, {0xc008d80090, 0x2, 0x18})
                    C:/go/src/reflect/value.go:542 +0x814
                reflect.Value.Call({0x63c8e0, 0x67a178, 0x5a33c0}, {0xc008d80090, 0x2, 0x2})
                    C:/go/src/reflect/value.go:338 +0xc5
                testing.(*F).Fuzz.func1.1(0x0)
                    C:/go/src/testing/fuzz.go:412 +0x20b
                testing.tRunner(0xc008d961a0, 0xc008d98090)
                    C:/go/src/testing/testing.go:1352 +0x102
                created by testing.(*F).Fuzz.func1
                    C:/go/src/testing/fuzz.go:401 +0x4df
                
        --- FAIL: FuzzFoo (0.00s)
            testing.go:1244: panic: runtime error: index out of range [1] with length 1
                goroutine 3189 [running]:
                runtime/debug.Stack()
                    C:/go/src/runtime/debug/stack.go:24 +0x90
                testing.tRunner.func1()
                    C:/go/src/testing/testing.go:1244 +0x545
                panic({0x65c2e0, 0xc000014180})
                    C:/go/src/runtime/panic.go:814 +0x207
                github.com/mattn/go-fuzz-example_test.doSomething(...)
                    C:/Users/mattn/go/src/github.com/mattn/go-fuzz-example/foo_test.go:6
                github.com/mattn/go-fuzz-example_test.FuzzFoo.func1(0x0, {0xc008d820b0, 0x0, 0x4f37d9})
                    C:/Users/mattn/go/src/github.com/mattn/go-fuzz-example/foo_test.go:15 +0xfb
                reflect.Value.call({0x63c8e0, 0x67a178, 0x30}, {0x66a8b0, 0x4}, {0xc008d80180, 0x2, 0x18})
                    C:/go/src/reflect/value.go:542 +0x814
                reflect.Value.Call({0x63c8e0, 0x67a178, 0x5a33c0}, {0xc008d80180, 0x2, 0x2})
                    C:/go/src/reflect/value.go:338 +0xc5
                testing.(*F).Fuzz.func1.1(0x0)
                    C:/go/src/testing/fuzz.go:412 +0x20b
                testing.tRunner(0xc008d96340, 0xc008d98120)
                    C:/go/src/testing/testing.go:1352 +0x102
                created by testing.(*F).Fuzz.func1
                    C:/go/src/testing/fuzz.go:401 +0x4df
                
    
    Crash written to testdata\fuzz\FuzzFoo\e3443030b91d09bbdd7fbe67dbbbc3093401b277aa711c82dd97ee80606f56ab
    To re-run:
    go test github.com/mattn/go-fuzz-example -run=FuzzFoo/e3443030b91d09bbdd7fbe67dbbbc3093401b277aa711c82dd97ee80606f56ab
FAIL
exit status 1
FAIL    github.com/mattn/go-fuzz-example    1.012s

バイト列以外の mutate に関しては現在は TODO の様です。

実装しておくだけで勝手にバグを見付けてくれるのですから、とても便利ですね。ぜひ活用していきましょう。

Posted at by