-
Notifications
You must be signed in to change notification settings - Fork 368
Fuzz testing
For further improving the stability of the software, some simple fuzz testing was implemented. This is making usage of the go-fuzz package and can easily be performed. The tests itself are all in the fuzz.go file.
We do have an extra branch for this, as not everyone needs the extended code for just running the software. Please checkout the branch tests for this:
$ git checkout tests
$ git pull
We try to keep the branch up to date with the master branch.
If you don’t have go-fuzz already installed, do that now.
$ go get github.com/dvyukov/go-fuzz/go-fuzz
$ go get github.com/dvyukov/go-fuzz/go-fuzz-build
Build the packages and you should get the go-fuzz as well as the go-fuzz-build tools.
(Assuming go get
will build these automatically and leave them in $GOPATH/bin
)
If you have pulled the recent tests branch you should now also have the workdir and within that the corpus folder. This contains some files that are used to initate the fuzzing. You are very welcome to create further corpus files, the ones we use are the example smtp files of the go-fuzz package.
See fuzz_test.go - run the TestGenerateCorpus test by itself to generate the corpus files, or add additional files there. The reason why a program is used to generate is because sometimes we can't use the text editor to insert non-printable characters that we want. For example, SMTP likes to have CR + LF at the end. To run TestGenerateCorpus by itself:
go test -v github.com/flashmob/go-guerrilla -run ^TestGenerateCorpus$
After everything is prepared you can start. Build the package with go-fuzz:
$ go-fuzz-build github.com/flashmob/go-guerrilla
This will take a while and create a file named guerrilla-fuzz.zip Now the fuzzing process itself can be started:
$ go-fuzz -bin=guerrilla-fuzz.zip -workdir=workdir -proc=1000
This will run for quite a while. Eventually you will get an output that contains crashers.
You can investigate on those crashes looking at the .output files in the workdir/crashers folder.
// Fuzz passes the data to the mock connection
// Data is random input generated by go-fuzz, note that in most cases it is invalid.
// The 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
func Fuzz(data []byte) int {
var wg sync.WaitGroup
// grab a new mocked tcp connection, it consists of two pipes (io.Pipe)
conn := mocks.NewConn()
// Get a client from the pool
poolable, err := fuzzServer.clientPool.Borrow(conn.Server, 1, logOff)
if c, ok := poolable.(*client); !ok {
panic("cannot borrow from pool")
} else {
mockClient = c
}
defer func() {
conn.Close()
// wait for handleClient to exit
wg.Wait()
// return to the pool
fuzzServer.clientPool.Return(mockClient)
}()
wg.Add(1)
go func() {
fuzzServer.handleClient(mockClient)
wg.Done()
}()
b := make([]byte, 1024)
if n, err := conn.Client.Read(b); err != nil {
return 0
} else if isFuzzDebug {
fmt.Println("Read", n, string(b))
}
// Feed the connection with fuzz data (we are the _client_ end of the connection)
if _, err = io.Copy(conn.Client, bytes.NewReader(data)); err != nil {
return 0
}
// allow handleClient to process
time.Sleep(time.Millisecond + 10)
for {
if mockClient.bufout.Buffered() == 0 {
break
}
b = make([]byte, 1024)
if n, err := conn.Client.Read(b); err != nil {
if isFuzzDebug {
fmt.Println(string(b), err)
}
return 1
} else if isFuzzDebug {
if isFuzzDebug {
fmt.Println("Read", n, string(b))
}
}
// allow handleClient to process
time.Sleep(time.Millisecond + 10)
if isFuzzDebug {
fmt.Println("buffered:", mockClient.bufout.Buffered())
}
}
return 1
}