README
¶
Continuous Fuzzing for Golang Example
Fuzzing for go can help find both complex bugs and correctness bugs. Go is a safe language so memory corruption bugs are very unlikely to happen, but some bugs can still have security implications.
This tutorial focuses less on how to build go-fuzz targets and more on how to integrate the targets with GitLab. A lot of great information is available at the go-fuzz repository.
This is an example of how to integrate your go-fuzz targets into GitLab Ci/CD
This example will show the following steps:
Result:
- go-fuzz targets will run continuously on the master branch
- go-fuzz targets will run regression tests on every pull-request (and every other branch) with the generated corpus and crashes to catch bugs early on.
Understanding the bug
The bug is located at parser_complex.go
in the following code
package parser
func ParseComplex(data [] byte) bool {
if len(data) == 6 {
if data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'I' && data[5] == 'N' && data[6] == 'G' {
return true
}
}
return false
}
This is the simplest example to demonstrate a classic off-by-one/out-of-bounds error which causes the program to crash.
Instead of len(data) == 6
the correct code will be len(data) == 7
.
Understanding the fuzzer
The fuzzer is located at parse_complex_fuzz.go
in the following code:
// +build gofuzz
package parser
func Fuzz(data []byte) int {
ParseComplex(data)
return 0
}
Building go-fuzz Target
The targets that are currently supported on GitLab are targets built in a GitLab pipeline that utilize the libFuzzer engine. This is why we will
use the -libfuzzer
flag of go-fuzz and compile it on a Linux machine (should be supported on mac via brew install clang
)
Running go-fuzz from CI
The best way to integrate go-fuzz fuzzing with Gitlab CI/CD is by adding additional stage & step to your .gitlab-ci.yml
.
include:
- template: Coverage-Fuzzing.gitlab-ci.yml
my_fuzz_target:
extends: .fuzz_base
image: golang:latest
variables:
CI_SEED_CORPUS: './seed_corpus'
script:
- apt update && apt install -y clang
- go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
- go-fuzz-build -libfuzzer -o my_fuzz_target.a .
- clang -fsanitize=fuzzer my_fuzz_target.a -o my_fuzz_target
- ./gitlab-cov-fuzz run --regression=$REGRESSION -- ./my_fuzz_target || true
For each fuzz target you will have to create a step which extends .fuzz_base
that runs the following:
- Builds the fuzz target
- Runs the fuzz target via
gitlab-cov-fuzz
CLI. - For
$CI_DEFAULT_BRANCH
(can be override by$COV_FUZZING_BRANCH
) will run fully fledged fuzzing sessions. For everything else including MRs will run fuzzing regression with the accumlated corpus and fixed crashes.
How to run fuzz testing locally
If you want to try running fuzz testing on your local machine rather than as part of a pipeline, you can follow the steps below to build the fuzz target and then run the fuzz target.
Building the fuzzer
cd /go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example
go-fuzz-build -libfuzzer -o parse-complex.a .
clang -fsanitize=fuzzer parse-complex.a -o parse-complex
Running the fuzzer
./parse-complex
Will print the following output and stacktrace:
INFO: Seed: 1994114449
INFO: 65536 Extra Counters
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED ft: 4 corp: 1/1b exec/s: 0 rss: 25Mb
#203 NEW ft: 6 corp: 2/7b lim: 6 exec/s: 0 rss: 25Mb L: 6/6 MS: 1 CrossOver-
#114009 NEW ft: 7 corp: 3/13b lim: 1130 exec/s: 38003 rss: 25Mb L: 6/6 MS: 1 ChangeByte-
#131072 pulse ft: 7 corp: 3/13b lim: 1290 exec/s: 43690 rss: 25Mb
#262144 pulse ft: 7 corp: 3/13b lim: 2600 exec/s: 37449 rss: 25Mb
#364834 NEW ft: 8 corp: 4/19b lim: 3623 exec/s: 36483 rss: 25Mb L: 6/6 MS: 5 ChangeByte-EraseBytes-ChangeByte-ShuffleBytes-InsertByte-
#432956 NEW ft: 9 corp: 5/25b lim: 4096 exec/s: 36079 rss: 25Mb L: 6/6 MS: 2 ChangeByte-ChangeByte-
#435537 NEW ft: 10 corp: 6/31b lim: 4096 exec/s: 36294 rss: 25Mb L: 6/6 MS: 1 CopyPart-
#524288 pulse ft: 10 corp: 6/31b lim: 4096 exec/s: 37449 rss: 25Mb
#1048576 pulse ft: 10 corp: 6/31b lim: 4096 exec/s: 36157 rss: 25Mb
#1669798 NEW ft: 11 corp: 7/37b lim: 4096 exec/s: 36299 rss: 25Mb L: 6/6 MS: 1 ChangeByte-
panic: runtime error: index out of range
goroutine 17 [running, locked to thread]:
gitlab.com/fuzzing-examples/example-go.ParseComplex.func6(...)
/go/src/gitlab.com/fuzzing-examples/example-go/parse_complex.go:5
gitlab.com/fuzzing-examples/example-go.ParseComplex(0x13dab00, 0x6, 0x6, 0xc000056000)
/go/src/gitlab.com/fuzzing-examples/example-go/parse_complex.go:5 +0x1e4
gitlab.com/fuzzing-examples/example-go.Fuzz(...)
/go/src/gitlab.com/fuzzing-examples/example-go/parse_complex_fuzz.go:6
main.LLVMFuzzerTestOneInput(0x13dab00, 0x6, 0x545b58)
/tmp/go-fuzz-build673188302/gopath/src/gitlab.com/fuzzing-examples/example-go/go.fuzz.main/main.go:35 +0x84
main._cgoexpwrap_98ba7f745c88_LLVMFuzzerTestOneInput(0x13dab00, 0x6, 0x13d9ab0)
_cgo_gotypes.go:64 +0x37
==857== ERROR: libFuzzer: deadly signal
#0 0x45bf30 in __sanitizer_print_stack_trace (/go/src/gitlab.com/fuzzing-examples/example-go/parse-complex+0x45bf30)
#1 0x43b5cb in fuzzer::PrintStackTrace() (/go/src/gitlab.com/fuzzing-examples/example-go/parse-complex+0x43b5cb)
#2 0x4220c3 in fuzzer::Fuzzer::CrashCallback() (/go/src/gitlab.com/fuzzing-examples/example-go/parse-complex+0x4220c3)
#3 0x7f76d88ed72f (/lib/x86_64-linux-gnu/libpthread.so.0+0x1272f)
#4 0x4aca90 in runtime.raise /tmp/go-fuzz-build673188302/goroot/src/runtime/sys_linux_amd64.s:149
NOTE: libFuzzer has rudimentary signal handlers.
Combine libFuzzer with AddressSanitizer or similar for better crash reports.
SUMMARY: libFuzzer: deadly signal
MS: 1 ChangeByte-; base unit: d0735caef6f40321673b1ce14e49a21aa86b2410
0x46,0x55,0x5a,0x5a,0x49,0x4e,
FUZZIN
artifact_prefix='./'; Test unit written to ./crash-14b5f09dd74fe15430d803af773ba09a0524670d
Base64: RlVaWklO