Go · Fuzz testing · Testing

Fuzzing with Go

According to the Wikipedia article on Fuzzing Fuzz testing “is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program.”

This is a very helpful technique for testing code, unit testing focuses on providing predetermined input that demonstrates a class of inputs are handled as expected, but Fuzz testing gives randomised input that better reflects normal usage.

For fuzz testing with Go there are a number of libraries, but the focus will be Google’s gofuzz library, and the actual fuzz testing tool, Dmitry Vyukov’s go-fuzz.

This post is not a rehash of tutorials such as Go Fuzz tutorial that can be found easily on the ‘net, I am going to focus on the ‘rules’ that need to be followed in order to get fuzz testing happening in your project.

The very first thing that needs to happen is for the system hosting the fuzz test to have the most up to date version of go-fuzz.

go get -v -u github.com/dvyukov/go-fuzz

Followed by building the binaries that will be used to do the tests (and putting them on your $PATH).

Note: I continue to use $GOPATH

cd $GOPATH/src/github.com/dvyukov/go-fuzz
cd go-fuzz
go build go-fuzz
mv go-fuzz $GOPATH/bin
cd ../go-fuzz-build
go build go-fuzz-build
mv go-fuzz-build $GOPATH/bin

The next thing to do is write some fuzz tests! The rules are:

  • The file name that contains the fuzz tests ends with _fuzz.go
  • The names of the tests that will be run by the fuzzer must start with Fuzz
  • The fuzz tests must be in the same package as the SUT (that is, not in the _test package)
  • Currently, regardless of how many fuzz tests you have, the fuzzer can only run one at a time
  • (Optional, but recommended) Add a build tag so your fuzz tests are disregarded for normal builds // +build gofuzz

Example authentication_fuzz.go

// +build gofuzz

// Package authentication -
package authentication

import (
	fuzz "github.com/google/gofuzz"
	"github.com/shanehowearth/appointment/authentication/integration/usermanager/v1"

	"golang.org/x/crypto/bcrypt"
)

// Fake types used for testing
type fakeUserStorage struct{}
type fakeTokenStorage struct{}

// Create the methods required to satisfy the interfaces that the fakes will be used for

// FetchUserDetails -
func (u *fakeUserStorage) FetchUserDetails(username, tenant string) (usermanager.User, error) {
	pass, _ := bcrypt.GenerateFromPassword([]byte(fakePassword), 14)
	return usermanager.User{Username: fakeUName, Tenant: fakeTenant, Password: string(pass)}, nil
}

// SendTokens -
func (t *fakeTokenStorage) SendTokens(external, internal string) error {
	return nil
}

// Variables in the package scope so they can be set inside the test functions, but their value can be used in the Fake methods. 
var fakeUName, fakeTenant, fakePassword string

// FuzzNames -
func FuzzNames(data []byte) int {
    // Prepare the Auth service with the fake types
	s := &fakeTokenStorage{}
	u := &fakeUserStorage{}
	au := NewAuthenticationService(u, s)

    // The Username will be populated with random data
	var fakeUName string
	fuzz.NewFromGoFuzz(data).Fuzz(&fakeUName)

	fakeTenant = "TestTenant"
	fakePassword = "TestPassword"

    // Call the method being tested with the test data
    // Note: We do not care about the output from the method
    // This test is purely about looking for crashes
	au.Validate(fakeUName, fakeTenant, fakePassword)

	return 0
}

// FuzzTenants -
func FuzzTenants(data []byte) int {
	s := &fakeTokenStorage{}
	u := &fakeUserStorage{}
	au := NewAuthenticationService(u, s)

	var fakeTenant string
	fuzz.NewFromGoFuzz(data).Fuzz(&fakeTenant)
	fakeUName = "TestUName"
	fakePassword = "TestPassword"
	au.Validate(fakeUName, fakeTenant, fakePassword)

	return 0
}

// FuzzPasswords -
func FuzzPasswords(data []byte) int {
	s := &fakeTokenStorage{}
	u := &fakeUserStorage{}
	au := NewAuthenticationService(u, s)

	var fakePassword string
	fuzz.NewFromGoFuzz(data).Fuzz(&fakeUName)
	fakeTenant = "TestTenant"
	fakeUName = "TestUName"
	au.Validate(fakeUName, fakeTenant, fakePassword)

	return 0
}

There are three Fuzz functions in the preceding example, but only one can be run at once. Once the tests have been written the go-fuzz tool needs to build and run them.

From the same directory that the tests are in the simplest (but slightly incomplete) approach is:

go-fuzz-build
go-fuzz -func FuzzNames

This should fill in any missing information that you might have about writing (and using) Fuzz tests in Go.

Published:
comments powered by Disqus