Skip to content

ci: minimize a fuzz input for 1s, not 10s - #1385

Open
fuad-daoud wants to merge 4 commits into
ci/nightly-fuzz-searchfrom
ci/fuzz-minimize-1s
Open

fuad-daoud wants to merge 4 commits into
ci/nightly-fuzz-searchfrom
ci/fuzz-minimize-1s

Conversation

@fuad-daoud

@fuad-daoud fuad-daoud commented Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Each search minimizes a new input for 1s, not 10s (-fuzzminimizetime). The flag governs every input that adds coverage, not only a crash, and a worker that is minimizing runs no search. A shorter bound can leave a crash's reproducer and each new corpus entry less minimal, a cost the measurement weighed: new inputs and coverage bits both rose at 1s. A search run by hand now defaults to 5s a target (fuzz.sh, the Makefile help and FUZZTIME, and the README). The nightly job's FUZZTIME: 1m is unchanged, and so is its timeout estimate: -fuzztime's deadline cancels minimizing too (go1.27 internal/fuzz/fuzz.go: the workers' context is a child of the -fuzztime timeout), so the bound never lengthens a target's search.

The 5s default is a second decision, carried here with the bound from the same commit. Once #1382 lands, the nightly job does the deep search, and a search run by hand is a quick local check before a push, so 5s halves its wait: about 75s of search over 15 targets instead of 150s. By this body's own figures it costs about a fifth of the new inputs such a search finds, 812 at 5s against 1,035 at 10s, both at the 1s bound.

Stacked on #1382, which moves the search to a nightly job and is stacked on #1373 and #1311; the diff here shows only this change. The stack lands bottom-up: once #1382 merges, this is retargeted to main (the repository does not delete merged branches, so GitHub will not retarget it), and Closes #1290 links there. This is split from Omar's 22cd99d0 (#1301), whose other changes are in #1382. Omar's d16302ac carries his author and date and his measurement in its message; a4dd4b41 then names the corpus cost in the fuzz.sh comment, and fe26eda1 cites the Go source it rests on.

Why 1s

Fresh corpus each run, three runs a side, alternating, -fuzztime=1m -parallel 4, coverage read from the corpus through GODEBUG=fuzzdebug=1, which reads 410 bits for FuzzNewBigVal_AcceptedFormsAreJSONValid's seeds alone against 854 after an 8s search:

Target Coverage bits, 10s Coverage bits, 1s New inputs, 10s New inputs, 1s
FuzzCycleDetector 40,006–41,090 42,402–42,766 33–71 240–269
FuzzNewBigVal_AcceptedFormsAreJSONValid 930–989 997–1,020 124–152 137–151

The cost #1290 names did not appear. Over four runs a side on a planted crash, reproducers were 2 or 3 bytes at both bounds, and 10s missed the crash once within 2 minutes. At a 10s budget across all 15 targets, 1s also found 1,035 new inputs against 863, and a search run by hand at the new 5s default found 812. This compares one shorter bound on two targets, plus the 15-target totals.

A second look at FuzzNewBigVal_AcceptedFormsAreJSONValid on this branch, two runs a side, -fuzztime=1m -parallel 4, same method: coverage bits 943 and 950 at 10s, 984 and 973 at 1s. New inputs 120 and 113 at 10s, 126 and 136 at 1s.

Out of scope

A deadline that reports no finding (#466) is left alone, as #1290 puts it.

Before and after

flowchart LR
  subgraph before["Before"]
    direction TB
    b1["a search finds a new input"] --> b2["a worker minimizes it for up to 10s<br/>and searches nothing meanwhile"]
    b2 --> b3["at a 10s budget, as long as the search itself"]
  end
  subgraph after["After"]
    direction TB
    a1["a search finds a new input"] --> a2["minimized for up to 1s"]
    a2 --> a3["more of each budget spent searching:<br/>more new inputs and coverage bits"]
    a2 -. "cost" .-> a4["reproducers and corpus entries<br/>can be less minimal"]
  end
  before ~~~ after
Loading

What it brings: each night's minute a target, and each search run by hand, spends its time finding inputs rather than shrinking them: on the measured targets 1s found more new inputs and more coverage than 10s.

Test plan

  • A go wrapper on PATH that logs go test arguments, then ./scripts/fuzz.sh with FUZZTIME unset: 15 go test lines, each -fuzztime=5s -fuzzminimizetime=1s.
  • FUZZTIME=1m: 15 lines, each -fuzztime=1m -fuzzminimizetime=1s; make -n fuzz prints ./scripts/fuzz.sh 5s.
  • Planted minimize_time=10s: the log shows -fuzzminimizetime=10s on all 15 lines. Reverted.
  • bash -n and shellcheck scripts/fuzz.sh; make fuzz-check.
  • make gate: this pull request's CI runs it on this head. No Go changed.

Closes #1290

Related: #1268, #1273, #1301, #1382

OmarAlJarrah and others added 4 commits November 3, 2026 01:30
Each search minimizes for 1s, not 10s: the flag also governs every input that
adds coverage, and a worker minimizing runs no search. A search run by hand
defaults to 5s.

Measured with a fresh corpus each run, three runs a side, alternating,
-fuzztime=1m -parallel 4, and coverage read from the corpus through
GODEBUG=fuzzdebug=1, which reads 410 bits for a target's seeds alone against
854 after an 8s search:

| Target                                  | Coverage bits, 10s | Coverage bits, 1s | New inputs, 10s | New inputs, 1s |
|-----------------------------------------|-------------------:|------------------:|----------------:|---------------:|
| FuzzCycleDetector                       |      40,006-41,090 |     42,402-42,766 |           33-71 |        240-269 |
| FuzzNewBigVal_AcceptedFormsAreJSONValid |            930-989 |       997-1,020   |         124-152 |        137-151 |

The cost a shorter bound could have is a larger reproducer, and it did not
appear: over four runs a side on a planted crash, reproducers were 2 or 3 bytes
at both bounds, and 10s missed the crash once within 2 minutes. This compares
one shorter bound on two targets. At a 10s budget across all 15 targets, 1s
also found 1,035 new inputs against 863, and a search run by hand now defaults
to 5s, which found 812.

Split from 22cd99d, whose other changes move the search to a nightly job.
The comment over minimize_time said a shorter bound costs at most a larger
reproducer. The flag also bounds the minimizing of every new input that adds
coverage, and Go keeps an input whose minimizing is cut short as it stands, so
a shorter bound can leave each new corpus entry less minimal too. Say so, so
the next reader knows what the bound trades.
The comment states what Go's fuzzing engine does with -fuzzminimizetime: it minimizes every coverage-adding input, not only a crash, and a worker that is minimizing runs no search. Name where that was read, as the workflow header does for the cache's trim: go1.27's internal/fuzz, fuzz.go's queueForMinimization and worker.go's loop.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants