1.2.10 Fuzz Testing & Crash Regression

extra

Fuzzing searches for inputs that can make a small API misbehave. A coverage-guided engine mutates inputs that reach new program paths, and useful discoveries become normal, replayable regressions. A long run does not prove the program correct.1

Recognize a useful fuzz target

The target should expose a small deterministic parser or API boundary. A small driver is easier for the engine to explore and for you to replay. Unit tests remain the right place for named examples and expected behavior.

Seed corpora give the engine a few representative starting inputs. Dictionaries add useful tokens such as delimiters or protocol keywords. Both are inputs to the fuzz target, so declare them rather than relying on files found on one developer machine.1

At this level, you operate an existing fuzz target: check that its corpus and dictionary are declared inputs, then focus on bounded execution, evidence, and replay. A maintainer supplies the language-specific harness that passes each generated input to the narrow API. For native targets, choosing compatible graph-wide instrumentation continues in L2.5.3 Sanitizers.

Run, preserve, replay

Bound local exploration by time. With the upstream asan-libfuzzer configuration copied into the project's .bazelrc, this command starts from a clean output directory and stops after 30 seconds:

$ bazel run -c opt --config=asan-libfuzzer \
    //parser:parser_fuzz_test_run -- --clean --timeout_secs=30

The launcher writes generated inputs under /tmp/fuzzing by default. A crash is evidence in the form of a saved input plus the sanitizer failure, not merely the word “failed” in a test summary. Copy the reproducing input into parser_corpus/ after confirming it is stable.

When a run finds a crash:

  1. preserve or minimize the input.
  2. replay it with the same target until failure is deterministic.
  3. fix the defect.
  4. keep the input in a regression corpus and run it with bazel test.

The replay engine runs the declared corpus without searching for new inputs:

$ bazel run -c opt --config=asan-replay \
    //parser:parser_fuzz_test_run -- --regression
$ bazel test -c opt --config=asan-replay //parser:parser_fuzz_test

The first command is the focused reproduction path. After the fix, the second must pass with the crashing input still present in parser_corpus/. That checked-in input is the lasting regression evidence.1

The replay step separates a useful discovery from a transient fuzzing session. Saved crashes also fit the output-investigation workflow in 1.2.7 Test Output & Debugging.

Continuous fuzzing belongs in a separate scheduled or hosted lane such as OSS-Fuzz. It can search for hours across engine and sanitizer combinations, while presubmit keeps bounded regression replay fast and predictable. The ruleset-maintenance side belongs in 4.11.5 Ruleset CI/CD Patterns, and fleet-scale test operation continues in H.7.2 Evidence Portfolio.1

key takeaway

Operate fuzz targets over small deterministic surfaces with declared seeds and dictionaries. Bound local searches and turn every useful crash into a deterministic checked-in regression. Reserve target authoring and instrumentation choices for the language specialist, and continuous exploration for a separate long-running lane.

Footnotes

  1. rules_fuzzing repository map — drivers, corpora, dictionaries, engine and sanitizer configurations, replay mode, and OSS-Fuzz integration. 1 2 3 4