| name | fuzzing-go-expert |
| description | Use this skill to fuzz open source Go software projects. |
Fuzzing Go expert
This skill provides the agent with the knowledge and tools to write, build, and
validate fuzz targets for Go projects integrated into OSS-Fuzz. Go fuzzing uses
the native Go fuzzing framework introduced in Go 1.18, which OSS-Fuzz drives
via libFuzzer under the hood using compile_native_go_fuzzer.
Fundamental Concepts
OSS-Fuzz base image
Go projects must use the Go base builder image:
FROM gcr.io/oss-fuzz-base/base-builder-go
Set language: go in project.yaml.
Harness structure
Go fuzz targets are standard Go test functions with the prefix Fuzz, placed
in _test.go files (or plain .go files that import the testing package):
package mypkg
import (
"testing"
_ "github.com/AdamKorcz/go-118-fuzz-build/testing"
)
func FuzzMyTarget(f *testing.F) {
f.Add([]byte("example input"))
f.Add([]byte("another seed"))
f.Fuzz(func(t *testing.T, data []byte) {
_, _ = ParseSomething(data)
})
}
The inner f.Fuzz callback signature can use typed parameters instead of
[]byte when the target expects structured input:
f.Fuzz(func(t *testing.T, s string, n int, b bool) {
_ = ProcessRecord(s, n, b)
})
Building in OSS-Fuzz
Use the compile_native_go_fuzzer helper in build.sh. It takes the package
import path, the function name, and the output binary name:
cp $SRC/fuzz_test.go ./
printf "package mypkg\nimport _ \"github.com/AdamKorcz/go-118-fuzz-build/testing\"\n" \
> register.go
go mod tidy
compile_native_go_fuzzer github.com/owner/repo/pkg FuzzMyTarget fuzz_my_target
For projects with multiple packages or multiple fuzz targets repeat the call:
compile_native_go_fuzzer github.com/owner/repo/pkg1 FuzzFoo fuzz_foo
compile_native_go_fuzzer github.com/owner/repo/pkg2 FuzzBar fuzz_bar
Seed corpus and dictionaries
- Seed corpus entries go in
$OUT/<fuzzer_name>_seed_corpus/ as individual
files, or as a zip at $OUT/<fuzzer_name>_seed_corpus.zip.
- Dictionaries go in
$OUT/<fuzzer_name>.dict as plaintext token files.
- Alternatively, add seeds directly via
f.Add(...) in the harness — these
are compiled in and used as the initial corpus.
- For targets that parse a structured format, generating seeds with a script
beats hand-picking a few files — random mutation rarely passes the parser's
early checks. See the structured seed generation
reference.
Characteristics of good Go fuzzing harnesses
- Targets attack surface: focus on parsers, decoders, protocol handlers,
serialisation/deserialisation, and any API that accepts untrusted bytes or
strings.
- Handles expected errors gracefully: wrap calls in error checks and ignore
expected error returns. Only genuine panics and unexpected behaviour are
findings.
- Uses typed fuzz parameters when the target is not purely byte-oriented —
Go's fuzzer can mutate
string, int, bool, float64, etc. directly.
- Avoids non-determinism: do not use random sources, time, goroutines, or
global state that persists between calls.
- Keeps the callback fast: expensive setup (e.g. parsing config, opening
files) belongs outside
f.Fuzz(...), not inside the inner function.
- Provides meaningful seeds:
f.Add(...) entries should be valid
representative inputs so coverage grows from the start.
- Does not get stuck: avoid code paths that busy-loop or block on I/O
inside the fuzz function.
- Includes the registration shim: the
import _ "github.com/AdamKorcz/go-118-fuzz-build/testing" blank import is required
for OSS-Fuzz to hook into native Go fuzzing — never omit it.
What Go fuzzing finds
Go is memory-safe, so the focus shifts from memory-corruption bugs to:
- Panics: index out of range, nil pointer dereference, type assertion
failures, stack overflows — any unrecovered
panic is a crash.
- Logic bugs: incorrect parsing, silent data corruption, wrong output for
valid input.
- Infinite loops / hangs: code that never returns on certain inputs
(detected by OSS-Fuzz's timeout).
- Incorrect error handling: code that should return an error but panics
instead, or vice versa.
Operational guidelines