Repository navigation
ci: minimize a fuzz input for 1s, not 10s - #1385
Open
fuad-daoud wants to merge 13 commits into
Open
fuad-daoud wants to merge 13 commits into
fuad-daoud wants to merge 13 commits into
Conversation
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.
fuad-daoud
marked this pull request as ready for review
November 2, 2026 23:28
This was referenced Nov 2, 2026
Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01P17gAP4FwSZUfAbRQmoNVc
fuad-daoud
marked this pull request as ready for review
November 7, 2026 05:34
This was referenced Nov 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
In short: When a fuzz search finds a new input, a worker stops searching to shrink it, for up to 10s. This bounds that at 1s, which found more new inputs and more coverage on the measured targets, and makes a search run by hand default to 5s a target instead of 10s; the nightly job's minute a target is unchanged.
Each search bounds minimizing a new input at 1s, not 10s (
-fuzzminimizetime). In a target taking a string or[]byte, 13 of the 15, 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, theMakefilehelp andFUZZTIME, and the README). The nightly job'sFUZZTIME: 1mis unchanged, and so is its timeout estimate:-fuzztime's deadline cancels minimizing too (go1.27internal/fuzz/fuzz.go: the workers' context is a child of the-fuzztimetimeout), 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.
A budget replays a target's corpus first, and the two decisions compound: the 1s bound keeps far more entries and the 5s default leaves less time to replay them. After
make fuzz FUZZTIME=5m, the 5s default ended insideFuzzCompile's replay of 1,404 entries at 759, searched nothing and exited 0, where a 10s budget on the same corpus, or the 10s bound's 393 entries at 5s, still reached the search.FuzzCycleDetectordid the same in the review's runs. Sofuzz.shnow names such a target:fuzz.sh: searched nothing, the budget ended replaying the corpus: FuzzCompile. Its exit stays 0, as no input failed. The nightly job is not reached: all 15 targets reached the search on two simulated nights.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), andCloses #1290links there. This is split from Omar's22cd99d0(#1301), whose other changes are in #1382. Omar'sd16302accarries his author and date and his measurement in its message;a4dd4b41then names the corpus cost in thefuzz.shcomment, andfe26eda1cites 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 throughGODEBUG=fuzzdebug=1, which reads 410 bits forFuzzNewBigVal_AcceptedFormsAreJSONValid's seeds alone against 854 after an 8s search:10s1s10s1sFuzzCycleDetectorFuzzNewBigVal_AcceptedFormsAreJSONValidThe cost #1290 names depends on the crash. Over four runs a side on a planted crash a short input reaches, reproducers were 2 or 3 bytes at both bounds, and 10s missed the crash once within 2 minutes. On one that needs a valid document, the shape of any
assertOraclesfailure, seven runs a side inFuzzCompileleft 366 to 3,825 bytes at 1s and 52 to 59 at 10s. A reproducer cut at 1s still replays.d16302ac's message still says the cost did not appear, so the squash body should leave that sentence out. 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. The table's runs name no machine: rerunning its method on linux/amd64 at8c5e7c21readFuzzCycleDetectorat 22,088–22,811 bits at 10s and 24,921–25,503 at 1s, with new inputs 90–139 and 339–400, so its direction holds and its magnitude does not reproduce, andFuzzNewBigVal_AcceptedFormsAreJSONValidat 970–986 and 993–1,016.A bound below 1s is not taken here. In the same review's runs, 100ms found more new inputs and coverage on three targets, with corpus bytes about 3 times larger, and found a small planted crash in 2 of 5 runs against 5 of 5 at 1s, too few runs to settle.
A second look at
FuzzNewBigVal_AcceptedFormsAreJSONValidon 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 ~~~ afterWhat it brings: each night's minute a target, and each search run by hand, spends less of its time shrinking inputs and finds more: on the measured targets 1s found more new inputs and more coverage than 10s.
Minimizing still dominates a search: at a 45s or 1m budget it took about 80 to 90% of the workers' time on
FuzzCompile,FuzzCycleDetectorandFuzzLowerSchemaand about 40% onFuzzNewBigVal_AcceptedFormsAreJSONValid, and at the 5s default about 70 to 99% on all four (a minimization cut at its bound logs0sand is charged at the bound).Test plan
gowrapper onPATHthat logsgo testarguments, then./scripts/fuzz.shwithFUZZTIMEunset: 15go testlines, each-fuzztime=5s -fuzzminimizetime=1s.FUZZTIME=1m: 15 lines, each-fuzztime=1m -fuzzminimizetime=1s;make -n fuzzprints./scripts/fuzz.sh 5s.minimize_time=10s: the log shows-fuzzminimizetime=10son all 15 lines. Reverted.FuzzCompilesearch left:./scripts/fuzz.shprints the line above, runs all 15 targets and exits 0. With itsgrepplanted out, no line. On seeds alone at 2s, no line. With a crash planted inFuzzNewBigVal_AcceptedFormsAreJSONValid: exit 1,fuzz.sh: failed:naming it, all 15 run. A SIGINT to the group: exit 130, one target started,go test's closing lines kept, no temporary file left.bash -nandshellcheck scripts/fuzz.sh;make fuzz-check.make gate: CI run 42342694556 passed on3c31b78c(lint, benchand the suite job success,fuzz searchskipped as a pull request runs no search; coverage gate: all 20273 statements covered). No Go changed.At merge, set the squash body by hand: a merge commit on this branch,
494bc457, carries attribution trailers that a default squash would paste ontomain.Closes #1290
Related: #1268, #1273, #1301, #1382