# Go fuzzing was missing half the toolkit. We forked the toolchain to fix it.

# Go fuzzing was missing half the toolkit. We forked the toolchain to fix it.

**By Kevin Valerio | May 12, 2026**

## Introduction

Go's native fuzzing capabilities lag significantly behind advanced tooling available in the Rust, C, and C++ ecosystems. The Go team at Trail of Bits recognized critical gaps in path constraint solving, structured input handling, and bug detection capabilities. In response, they created gosentry, a purpose-built fuzzing fork of the Go toolchain that maintains familiar developer workflows while leveraging more sophisticated fuzzing infrastructure underneath.

## Why we built gosentry

The development team encountered recurring obstacles when using Go's standard fuzzer for security research:

- Complex conditional branches created unsolvable path constraints
- Grammar-based fuzzing approaches were unavailable
- Type-aware fuzzing demanded manual intervention
- Several Go-specific bug classes—integer overflows, goroutine leaks, data races, and execution timeouts—wouldn't trigger failures automatically
- Campaign analysis and reporting proved cumbersome
- Detecting critical error conditions required source code modifications

These limitations motivated building a more capable alternative that preserves the familiar testing interface developers already know.

## Same harness, stronger engine

Gosentry maintains backward compatibility with existing Go fuzzing patterns. Developers write fuzz targets using the standard `testing.F` API:

```go
func FuzzExample(f *testing.F) {
    f.Add(exampleValue)
    f.Fuzz(func(t *testing.T, input []byte) {
        TargetFunction(input)
    })
}
```

The implementation difference is significant: gosentry "captures the fuzz callback, builds a Go archive with libFuzzer-style entry points, and runs it in-process through a Rust-based LibAFL runner." This architectural choice allows teams to leverage existing harnesses without porting them to new frameworks.

## More bugs become visible

Gosentry detects failure modes that vanilla Go fuzzing misses. The toolchain includes compiler-inserted integer overflow checks and integrates go-panikint for truncation detection. Additional capabilities include:

- Race condition detection via the native Go race detector
- Goroutine leak identification through goleak integration
- Timeout detection for identifying infinite loops
- Custom panic triggers using the `--panic-on` flag for codebases that log errors without crashing

## Better inputs

### Struct-aware fuzzing

Native Go fuzzing restricts inputs to basic types, excluding composite structures. Gosentry extends support to structs, slices, arrays, and pointers:

```go
type Input struct {
    Data []byte
    S    string
    N    int
}

func FuzzStructInput(f *testing.F) {
    f.Add(Input{Data: []byte("hello"), S: "world", N: 42})
    f.Fuzz(func(t *testing.T, in Input) {
        Process(in)
    })
}
```

The fuzzer still mutates bytes internally but handles encoding and decoding transparently, eliminating the need for custom wire format development.

### Grammar-based fuzzing

Gosentry integrates Nautilus for grammar-guided input generation. Rather than producing random mutations that fail early parsing stages, grammar mode generates structurally valid inputs matching defined rules:

```go
func FuzzGrammarJSON(f *testing.F) {
    f.Add(`{"postOfficeBox":123}`)
    f.Fuzz(func(t *testing.T, jsonInput string) {
        ParseJSONFromString(jsonInput)
    })
}
```

Grammar rules are specified as JSON arrays:

```json
[
  ["Json", "\\{\"postOfficeBox\":{Number}\\}"],
  ["Number", "{Digit}"],
  ["Number", "{Digit}{Number}"],
  ["Digit", "0"],
  ["Digit", "1"],
  ...
]
```

## What it has found already

Early campaigns using grammar-based differential fuzzing uncovered vulnerabilities in critical infrastructure projects:

- **Optimism/Kona Protocol**: Unknown batch type handling caused denial-of-service panics
- **Optimism Projects**: Brotli channel disagreement between Kona and op-node implementations
- **Optimism Stack**: Frame parsing mismatches against specification
- **Revm**: Failed deposit handling failed to bump nonce, causing state root mismatches with other clients

These represent exactly the failure modes that standard Go fuzzing would struggle to discover.

## Usage and availability

The basic usage pattern leverages familiar CLI conventions:

```bash
./bin/go test -fuzz=FuzzHarness --focus-on-new-code=false --catch-races=true --catch-leaks=true
```

Coverage reports can be generated from existing campaigns:

```bash
go test -fuzz=FuzzTarget --generate-coverage
```

Gosentry is available on GitHub with comprehensive documentation for each feature.
