Home Fuzzing with Go-Fuzz
Post
Cancel

Fuzzing with Go-Fuzz

Fuzzing can often be a very useful technical for finding bugs. Go-fuzz is a coverage-guided fuzzing solution for testing of Go packages. go-fuzz. This blog post will walk you through how to use it to find bugs.

1) Installing Go

On linux, Go can be quickly installed by the following commands.

1
2
3
4
5
$ cd /tmp
$ ls
$ curl -OL https://golang.org/dl/go1.15.12.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xvf go1.15.12.linux-amd64.tar.gz
$ go version

2) Choosing a target

Some easy ways to find a target for fuzzing can be

  • https://pkg.go.dev/
  • https://github.com/trending/go
  • https://github.com/avelino/awesome-go

For this example, we will fuzz https://github.com/JoshVarga/svgparser

3) Building pdf package for fuzzing

Go-fuzz can be installed by running the following commands

1
2
go get -u github.com/dvyukov/go-fuzz/go-fuzz
go get -u github.com/dvyukov/go-fuzz/go-fuzz-build

This installs the latest version of go-fuzz onto your go path

The svg target we are fuzzing can be installed by go get -u github.com/JoshVarga/svgparser which will install the svg reader onto your go path as well.

1
2
3
4
/home/snoopy/gowork/src/github.com/JoshVarga/svgparser
❯ ls
 example_test.go   find.go   find_test.go   fuzz.go  ο€– LICENSE   parser.go   parser_test.go  ο’Š README.md   testutils_test.go  ο„• utils
❯ 

This will install the svg package we are fuzzing in our go path, you can now go to this directory and create a new file call fuzz.go which will contain your harness.

Within Svgparser, we will try to fuzz the Parse function

https://github.com/JoshVarga/svgparser/blob/5eaba627a7d11a384dde3802ac251442e14d87ef/parser.go#L121

To fuzz this function, we can create a fuzz function harness for go-fuzz with the same package name. (Note: your fuzz.go file should be on the same folder as the function you are fuzzing). This function must return 1 if the fuzzer should increase priority of the given input during subsequent fuzzing (for example, the input is lexically correct and was parsed successfully); -1 if the input must not be added to corpus even if gives new coverage; and 0 otherwise; other values are reserved for future use. The Fuzz function must be in a package that go-fuzz can import. This means the code you want to test can’t be in package main. Fuzzing internal packages are also supported.

An examples fuzz function for fuzzing can be seen below (which is saved within /home/snoopy/gowork/src/github.com/JoshVarga/svgparser)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package svgparser

import (
	"bytes"
)

func Fuzz(data []byte) int {

	element,err := Parse(bytes.NewReader(data), false)
	if err != nil {
		if element != nil {
			panic("svg != nil on error")
		}
		return 0
	}

	return 1

}

This implements Fuzz(data []byte) function, which is an API expected by go-fuzz. once this has been done, you can go back to your working directory and use go-fuzz-build to build the archive which go-fuzz will use.

1
2
3
4
5
❯ pwd
/home/snoopy/svg_fuzz
❯ go-fuzz-build github.com/JoshVarga/svgparser
 βœ— ls
  svgparser-fuzz.zip

which will produce svgparser-fuzz.zip archive.

3) Building a corpus

The next step is to build a corpus for gofuzz to use which will be placed within your working directory. Ideally, files in the corpus are as small as possible and as diverse as possible. You can use inputs used by unit tests, examples and/or generate them manually. Go-fuzz by default will deduplicate and minimize the inputs

Go-fuzz will also add own inputs to the corpus directory. Furthermore, the go-fuzz-corpus repository contains a bunch of examples of test functions and initial input corpuses for various packages. https://github.com/dvyukov/go-fuzz-corpus.

In this instance, example svg files can be taken from https://github.com/strongcourage/fuzzing-corpus

Another option would be to create an example svg file and use radamsa to mutate the files to create some examples. Gofuzz will also mutate the files given and create more files for your corpus

These files can be taken and copied to a folder called corpus within you fuzzing directory.

3) Fuzzing

going back to your working directory, you can now start gofuzz: go-fuzz -bin=svgparser-fuzz.zip -workdir=. The working directory here should contain a folder corpus with all the svg files your previous found.

As long as you don’t delete working directory, you can stop and re-start go-fuzz and it will restart where it stopped. Found crashes will be logged in crashers directory.

  • workers means number of tests running in parallel (set with -procs flag).
  • corpus is current number of interesting inputs the fuzzer has discovered, time in brackets says when the last interesting input was discovered
  • crashers is number of discovered bugs (check out workdir/crashers dir).
  • restarts is the rate with which the fuzzer restarts test processes.
  • cover is number of bits set in a hashed coverage bitmap

4) Triaging Crashes

For any unique crashes, these will be logged within your crashers directory

1
2
3
4
5
❯ cd crashers/
❯ ls
ο€– 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7         ο€– 4465097c05a30de5b06942b3f02e4e196276ae49         ο€– 88ffb7f7327afe041ac722aaccc9b248b1a12524         ο€– c5f797d236f6199102cc91083335469aac76cece
ο€– 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7.output  ο€– 4465097c05a30de5b06942b3f02e4e196276ae49.output  ο€– 88ffb7f7327afe041ac722aaccc9b248b1a12524.output  ο€– c5f797d236f6199102cc91083335469aac76cece.output
ο€– 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7.quoted

These will be the format, fuzz input which is the input used to fuzz the file, the .quoted file contains the input quoted as a string and the .output file contains the crash dump

Example:

1
2
❯ cat 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7
<svg xmlns="htt00000000000000000000000">00</svg>0
1
2
3
4
5
❯ cat 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7.quoted 
	"<svg xmlns=\"htt00000" +
	"000000000000000000\">" +
	"00</svg>0"

1
2
❯ cat 0106e07494ad508ca0ec6e32d0ae9ac7bb932de7.output 
SVG width: panic: runtime error: index out of range [0] with length 0

This can then be triaged by creating a small program with the input, and using a go debugger to see the stack properly and what part of the code is crashing.

Another option is also to use libfuzzer

1
2
3
4
go-fuzz-build -libfuzzer -o svg.a .
go-fuzz-build -libfuzzer -o svg.a .
clang -fsanitize=fuzzer svg.a -o fuzz_svg
./fuzz_svg corpus_folder

Further Reading

This post is licensed under CC BY 4.0 by the author.