Skip to content

The weekly fuzz never compounds, and a find would lose its reproducer #225

Description

@Jaro-c

I measured what the weekly fuzz run actually does, because a workflow that always passes can mean either "nothing is broken" or "nothing is being explored". Two of my three suspicions were wrong; the third is real.

What holds up

The targets carry oracles rather than only checking for panics — FuzzParseID requires the returned id to be canonical 32-char hex and to appear in the input, the email target requires the error path to return an empty string and the success path to be canonical. That is the difference between fuzzing and running random bytes through a function.

The 60s budget is also well chosen. Measured cold, with an isolated GOCACHE:

Target Execs in 60s New interesting
FuzzParseJWK 3,432,098 219
FuzzFromPEM 7,045,478 270

At 300s — five times the budget — FuzzParseJWK reaches 293. A 34% gain for 5x the time: exploration saturates well before the cutoff, so 60s captures roughly three quarters of what a much longer run finds. Do not raise fuzztime on these numbers.

What does not

The corpus never accumulates. From today's run log:

Cache hit occurred on the primary key setup-go-…, not saving cache.

setup-go restores GOCACHE, which is where Go keeps the fuzzing corpus, but on a cache hit it does not save it again. Everything the fuzzer discovers during a run dies with the runner, and next week restores the same stale cache and re-explores the same space. The cache key derives from go.sum, which Dependabot changes constantly, so even the accidental persistence resets every few days.

The consequence is worth stating plainly: no matter how many weeks this runs, it will never reach deeper than 60s from the seed corpus can reach. The compounding that is the entire premise of continuous fuzzing does not happen here. The reusable is honest about this — it calls itself "a smoke run, not a corpus-building soak" — so this is a design point, not a defect. But it should be a decision rather than a surprise.

A find would lose its reproducer. When go test -fuzz fails it writes the failing input under testdata/fuzz/<Target>/ in the workspace and the job goes red — and the runner is destroyed without uploading anything. The failure survives only as log text. There is no testdata/fuzz in this repository either, so a discovered crash never becomes a permanent regression test.

Proposed

  1. Upload testdata/fuzz as an artifact when the job fails. Cheap and uncontroversial: today a find degrades into a log entry. This lives in the go-fuzz.yml reusable in Glyndor/.github.
  2. Decide whether this organisation wants fuzzing that compounds. If yes, the mechanism is a dedicated cache key for the corpus, decoupled from go.sum, saved on every run — not committing hundreds of machine-generated blobs, which is not idiomatic. For a security library I would argue yes, but it is more work and it is a product decision rather than a fix.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions