Fuzz Testing

Parsers, decoders, and protocol handlers face input that no developer anticipated: truncated files, deeply nested structures, invalid encodings, numbers at the edge of their range. Fuzz testing (fuzzing) feeds a program enormous numbers of generated inputs and watches for crashes, hangs, memory errors, and failed assertions. Modern coverage-guided fuzzers make this remarkably effective: they mutate inputs, keep the ones that reach new code paths, and gradually explore deep into a program's logic.

Fuzzing has found many thousands of security vulnerabilities in widely used software, which is why it is a standard part of secure development for anything that parses untrusted data. It is no longer exotic: Go, Rust, Python, and Java all have practical fuzzing tools that run alongside normal tests.

TL;DR

Quick Example

Go has fuzzing built into go test. A fuzz target for a query-string parser that checks it never crashes and that encoding round-trips:

When the fuzzer finds a failing input, it writes it to testdata/fuzz/FuzzParseQuery/. From then on, a plain go test replays it as a regression test, even without fuzzing enabled.

Core Concepts

Fuzz Targets

A fuzz target receives generated data and passes it to the code under test. Good targets are fast (thousands of executions per second), deterministic, free of side effects such as network calls or global state, and focused on one parser or API. Beyond crashes, they can check properties: round trips, comparison with a reference implementation, or invariants.

Coverage Guidance

The fuzzer instruments the program to see which code each input executes. Inputs that reach new branches join the corpus and are mutated further — flipping bits, inserting bytes, splicing inputs together. Over time the fuzzer constructs inputs that pass checksums, magic numbers, and nested structure checks that random data would never get through.

Tools by Language

A C or C++ target for libFuzzer:

Sanitizers

Many memory bugs do not crash on their own; they corrupt data silently. AddressSanitizer detects out-of-bounds accesses and use-after-free, UndefinedBehaviorSanitizer detects undefined behavior such as signed overflow, and MemorySanitizer detects reads of uninitialized memory. Fuzzing with sanitizers turns silent corruption into an immediate, reproducible crash with a stack trace.

Seeds, Dictionaries, and Structure

Crash Triage

  1. Reproduce the crash with the saved input.
  2. Minimize it — libFuzzer's -minimize_crash=1, AFL++'s afl-tmin — to the smallest input that still fails.
  3. Deduplicate by stack trace; one bug often produces many crashing inputs.
  4. Assess severity: memory corruption in code handling untrusted input is often a security vulnerability.
  5. Fix and add the minimized input to the regression corpus so it is tested forever. See Security Regression Test Suites.

Fuzzing in the Development Process

ClusterFuzzLite brings short fuzzing runs to CI systems for any project. Continuous campaigns reuse and grow the same corpus over time, which is where most deep bugs are found.

Best Practices

Fuzz Code That Handles Untrusted Input First

File parsers, network protocol handlers, decompressors, deserializers, and template engines give the best return. Pure business logic is usually better served by property-based tests.

Keep Targets Fast and Deterministic

Avoid I/O, sleeps, randomness, and global state inside targets. A target running a hundred thousand executions per second explores far more than one running a hundred.

Commit the Corpus and Crashes

Store the seed corpus and minimized crash inputs in the repository (or a corpus store) so every run builds on previous work and every fixed bug stays fixed.

Measure Coverage

Look at which code the corpus reaches. Unreached areas suggest missing seeds, a dictionary, or a target that needs restructuring.

Common Mistakes

Fuzzing Without Sanitizers

Targets That Swallow Everything

Fixing Crashes Without Keeping the Input

FAQ

What is fuzz testing?

An automated technique that feeds a program large numbers of generated, often malformed inputs to find crashes, hangs, memory errors, and failed assertions. Coverage-guided fuzzers use feedback about which code each input executes to explore the program efficiently.

How is fuzzing different from property-based testing?

They overlap. Fuzzers usually work on raw bytes, run for long periods, and use coverage guidance to find crashes and memory errors. Property-based testing generates structured values from strategies, runs quickly inside unit test suites, and checks logical properties. Many teams use both.

How long should I fuzz?

A few minutes per target in pull requests catches shallow bugs; deep bugs often take hours or days of continuous fuzzing. Corpus reuse means each run continues where previous runs left off.

Do I need sanitizers?

For C and C++ and other memory-unsafe code, yes — many memory errors do not crash without them. Memory-safe languages benefit less, but fuzzing still finds panics, infinite loops, excessive memory use, and logic errors.

What should I fuzz first?

Code that parses or decodes untrusted input: file formats, network protocols, serialization, compression, and anything exposed to users or the internet.

Related Topics

References