Generating inputs to a program in the fuzzing target

Fuzzing provides automated testing where a fuzzing engine continuously generates inputs to a program in the fuzzing target. The fuzzing feature of Go supports several built-in types.

With fuzzing, random data is run against your test to find issues such as bugs, vulnerabilities, or crash-causing inputs. For example, some examples of vulnerabilities that can be found by fuzzing are SQL injection, buffer overflow, denial of service, and cross-site scripting attacks.

In the unit test, you must add each input to the test. Fuzzing comes up with inputs for your code so that you can generate more input with less work.

Example

Before the testing begins, you need to create two files go.mod and fz_test.go. Here is a module example: module example/fuzz/go 1.18. The following example shows how to perform testing using fuzzing.

package main

import (
	"fmt"
	"hash/crc32"
	"testing"
)

func Foo(d1 string, d2 []byte) (err error) {
	crc32a := crc32.MakeTable(0x235312AB)
	crc32b := crc32.MakeTable(0xC342f421)
	a := crc32.Checksum([]byte(d1), crc32a)
	b := crc32.Checksum(d2, crc32b)
	if len(d1) > 4 && len(d2) > 4 {
		if a == b {
			err = fmt.Errorf("Collision %q, %q  crc32 %08x", d1, d2, a)
		}
	}
	return
}

func FuzzFoo(f *testing.F) {
	fmt.Printf("Into FuzzFoo\n")

	testcases := []string{"test-1", "test-2", "test-3", "test-4"}
	for _, tc := range testcases {
		fmt.Printf("Adding %s %s\n", tc, []byte{1, 2, 3, 4})
		f.Add(tc, []byte{1, 2, 3, 4})
	}
	f.Fuzz(func(t *testing.T, data string, data2 []byte) {
		err := Foo(data, data2)
		if err != nil {
			t.Errorf("Error: %s\n", err)
		}
	})
}

When the test fails, a directory called "testdata" will be created, which contains files related to the failure detected. If you rerun the test, the test data that produces that failure will be loaded and the particular test case will be rerun, because it is assumed that you fixed the code and want to run it again for verification.

If testdata is not present, you can run with go test without -fuzz argument:
go test -v

=== RUN   FuzzFoo
Into FuzzFoo
Adding test-1
Adding test-2
Adding test-3
Adding test-4
=== RUN   FuzzFoo/seed#0
=== RUN   FuzzFoo/seed#1
=== RUN   FuzzFoo/seed#2
=== RUN   FuzzFoo/seed#3
--- PASS: FuzzFoo (0.00s)
    --- PASS: FuzzFoo/seed#0 (0.00s)
    --- PASS: FuzzFoo/seed#1 (0.00s)
    --- PASS: FuzzFoo/seed#2 (0.00s)
    --- PASS: FuzzFoo/seed#3 (0.00s)
PASS
ok  	example/fuzz	0.130s
The test is only run with the four scenarios that are supplied through the "seed corpus". If you run with -fuzz=Fuzz, you can get the following result:
#go test -fuzz=Fuzz -v

=== FUZZ  FuzzFoo
Into FuzzFoo
Adding test-1
Adding test-2
Adding test-3
Adding test-4
warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient
fuzz: elapsed: 0s, testing seed corpus: 0/4 completed
fuzz: elapsed: 0s, testing seed corpus: 4/4 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 216667 (72195/sec)
fuzz: elapsed: 6s, execs: 580526 (121314/sec)
fuzz: elapsed: 9s, execs: 1083897 (167774/sec)
fuzz: elapsed: 12s, execs: 1716489 (210884/sec)
fuzz: elapsed: 15s, execs: 2449549 (244323/sec)
fuzz: elapsed: 18s, execs: 3182631 (244356/sec)
fuzz: elapsed: 21s, execs: 3914517 (244028/sec)
fuzz: elapsed: 24s, execs: 4644885 (243418/sec)
fuzz: minimizing 86-byte failing input file
fuzz: elapsed: 26s, minimizing
--- FAIL: FuzzFoo (26.34s)
    --- FAIL: FuzzFoo (0.00s)
        fz_test.go:33: Error: Collision "\xff\xff\xff\xff\x00", "\xff\xff\xff\xff\x00"  crc32 ffffffff

    Failing input written to testdata/fuzz/FuzzFoo/9402ce22f976a53cf5cd596e4b00b4e090f629d6a33f166cc8e0fd161fcf8022
    To re-run:
    go test -run=FuzzFoo/9402ce22f976a53cf5cd596e4b00b4e090f629d6a33f166cc8e0fd161fcf8022
FAIL
exit status 1
FAIL	example/fuzz	27.258s
More scenarios are generated by fuzzing in addition to the four "seed corpus" cases. The test in this case is designed to fail for demonstration purpose. After the execution, the testdata directory will be created:
#find testdata
testdata
testdata/fuzz
testdata/fuzz/FuzzFoo
testdata/fuzz/FuzzFoo/9402ce22f976a53cf5cd596e4b00b4e090f629d6a33f166cc8e0fd161fcf8022
If you run again, only the data that produced the failure will be rerun:
#go test -fuzz=Fuzz -v

=== FUZZ  FuzzFoo
Into FuzzFoo
Adding test-1
Adding test-2
Adding test-3
Adding test-4
warning: the test binary was not built with coverage instrumentation, so fuzzing will run without coverage guidance and may be inefficient
fuzz: elapsed: 0s, testing seed corpus: 0/5 completed
failure while testing seed corpus entry: FuzzFoo/9402ce22f976a53cf5cd596e4b00b4e090f629d6a33f166cc8e0fd161fcf8022
fuzz: elapsed: 2s, testing seed corpus: 4/5 completed
--- FAIL: FuzzFoo (1.82s)
    --- FAIL: FuzzFoo (0.00s)
        fz_test.go:33: Error: Collision "\xff\xff\xff\xff\x00", "\xff\xff\xff\xff\x00"  crc32 ffffffff

FAIL
exit status 1
FAIL	example/fuzz	3.144s
Note: You need to configure PARMLIB member SMFLIMxx to use fuzzing. To implement the values in SMFLIMxx, issue the z/OS® command "SET SMFLIM=(xx)", and then update the system PARMLIB member IEASYSxx to include a statement to imitate SMFLIM at IPL time.