A fuzzer saves crashing inputs by the dozen, and many are the same bug. Triage in five steps: group by stack trace, minimize one input per group, reproduce it with the matching sanitizer build, classify the bug type, and file it with the input attached. Do the grouping first, because it cuts the pile down most.
Step 1: group duplicates
Fuzzers dedupe imperfectly. AFL++ counts a crash as unique if its path includes a transition not seen in earlier crashes, so one bug reached in several ways can inflate the count early. libFuzzer names files by a hash of the input, so different inputs do not collapse together.
Group by the top few frames of the sanitizer stack trace instead. Same function and line in the top frames usually means the same bug. Keep one input per group.
Step 2: minimize
A minimized input is easier to read and debug. libFuzzer has -minimize_crash=1, used with -runs or -max_total_time to bound the attempts. AFL++ has afl-tmin. cargo-fuzz has cargo fuzz tmin. Go minimizes failing inputs automatically and writes them under testdata/fuzz.
Check the minimized input still crashes the same way. Occasionally minimization slides into a different bug, which is itself a finding.
Step 3: reproduce reliably
Replay the single input with the build that found it. Match the sanitizer: the OSS-Fuzz guidance says to build with the one matching the bug type, for example AddressSanitizer for buffer overflows. If you cannot reproduce, the OSS-Fuzz reproduction page suggests two things: pull fresh images, and try a 32-bit build, because some pointer and integer overflow bugs show up in only one mode.
Also check timeouts and memory limits. The same page mentions adding a timeout flag for timeouts and an RSS limit for out-of-memory bugs.
Step 4: classify
Read the sanitizer headline, which names the bug type. Typical buckets:
- Memory safety: out-of-bounds access, use-after-free, double free.
- Undefined behavior: signed overflow, bad shifts, null dereference.
- Resource exhaustion: timeouts, out-of-memory, stack overflow.
- Logic failure: your own assertion tripped.
Resource exhaustion may still matter. Against a public service, one request that burns a minute of CPU is a denial of service.
Step 5: file with a reproducer
A good report has the target name, the minimized input, the exact command, the sanitizer report, and the build flags. Whoever owns the code should be able to run one command and see the crash. After the fix, add the input to the regression corpus.
OSS-Fuzz notes that only one timeout and one out-of-memory bug are reported per target at a time, and that a renamed target closes its existing bugs and restarts from the seed corpus. Plan your own tracker conventions the same way: one issue per root cause, not per input file.
Try it yourself
Open the vulnerability management demo and watch how findings are sorted and tracked. Fuzzer crashes need the same discipline: one owner, one status, one due date.
Click through it at your own pace, or open it full screen.
Quick check
Should I fix every crash the fuzzer finds?
Fix or deliberately accept each root cause. Crashes in code that attackers can reach with their own input should go first.
The crash will not reproduce. Is it real?
Often it is, but depends on build or environment. Try the matching sanitizer build, fresh images, and a 32-bit build, and check for global state in the target.
Sources
- LLVM: libFuzzer
- AFL++: afl-fuzz approach
- OSS-Fuzz: reproducing bugs
- OSS-Fuzz: FAQ
- Go: native fuzzing
- cargo-fuzz
Last reviewed: 19 October 2026.