Fuzz Testing
What it is
Section titled “What it is”Fuzz testing throws vast amounts of malformed, random or adversarially mutated input at a program and watches for crashes, hangs, panics and memory errors. Unlike property-based testing, the goal is not a stated invariant — it is simply does it survive?
A coverage-guided fuzzer keeps input that reaches new code paths and mutates it further, so it explores deeper over time. When it finds a crash it saves the exact input as a crash reproducer.
seed corpus ──► mutate ──► run target ▲ │ │ new coverage? │ └──────── keep ──────────┤ ▼ crash / hang / sanitizer │ ▼ save reproducer + stack traceModern fuzzers pair with sanitizers — AddressSanitizer, MemorySanitizer, UndefinedBehaviorSanitizer — to catch silent memory misuse, not just segfaults.
When to use it / when to avoid it
Section titled “When to use it / when to avoid it”Use it for
- Parsers and decoders for untrusted input: JSON, images, protocols, archives
- Memory-unsafe code (C/C++/Rust
unsafe), where a crash may be exploitable - Security-sensitive surfaces: auth, crypto glue, deserialisation
- Continuous fuzzing in CI (OSS-Fuzz style) once a target exists
Avoid it when
- The input space is tiny and enumerable — table-driven tests cover it
- There is no clear crash oracle and no sanitizer — nothing will be flagged
- The code is pure and already exhaustively tested with properties
- You expect it to prove correctness; fuzzing finds crashes, not wrong answers
Frameworks & tools
Section titled “Frameworks & tools”| Tool | Ecosystem | Notes |
|---|---|---|
| libFuzzer | C/C++ (LLVM) | In-process, coverage-guided; part of LLVM |
| AFL++ | C/C++ | Instrumentation, forkserver; afl-fuzz |
| Atheris | Python | Coverage-guided fuzzing for CPython |
| Jazzer | Java/JVM | JVM fuzzing, integrates with JUnit 5 |
| cargo-fuzz | Rust | libFuzzer front-end for Rust, needs nightly |
| go-fuzz / native fuzzing | Go | go test -fuzz built in since Go 1.18 |
| OSS-Fuzz | Multi | Google’s continuous fuzzing service |
| ClusterFuzzLite | Multi | Run OSS-Fuzz in your own CI |
Quick setup
Section titled “Quick setup”# JS has no in-process coverage fuzzer; drive a native targetnpm install -D jazzer.jsnpx jazzer fuzz/parse_target.js -- -runs=100000pip install atherispython fuzz_parse.py -max_total_time=60go test -fuzz=FuzzParse -fuzztime=60s ./parser<dependency> <groupId>com.code-intelligence</groupId> <artifactId>jazzer-junit</artifactId> <version>0.22.1</version> <scope>test</scope></dependency># SharpFuzz drives AFL++/libFuzzer against .NET assembliesdotnet tool install -g SharpFuzz.CommandLinesharpfuzz MyApp.Parser.dllExample
Section titled “Example”const { FuzzedDataProvider } = require('@jazzer.js/core');
module.exports.fuzz = function (data) { const provider = new FuzzedDataProvider(data); const input = provider.consumeRemainingAsString(); try { parseConfig(input); } catch (e) { if (!(e instanceof ParseError)) throw e; }};import atheriswith atheris.instrument_imports(): from parser import parse_config
def TestOneInput(data: bytes): try: parse_config(data.decode("utf-8", "ignore")) except ParseError: pass # expected for bad input
atheris.Setup([], TestOneInput)atheris.Fuzz()func FuzzParse(f *testing.F) { f.Add([]byte("{\"port\":8080}")) f.Fuzz(func(t *testing.T, data []byte) { // must never panic, whatever the bytes are _, _ = ParseConfig(data) })}import com.code_intelligence.jazzer.api.FuzzedDataProvider;import com.code_intelligence.jazzer.junit.FuzzTest;
class ParserFuzzTest { @FuzzTest void fuzzParse(FuzzedDataProvider data) { String input = data.consumeRemainingAsString(); try { Parser.parse(input); } catch (ParseException expected) { // bad input is fine; a crash is not } }}using SharpFuzz;
public static class Program{ public static void Main(string[] args) => Fuzzer.Run(data => { try { Parser.Parse(data); } catch (ParseException) { /* expected */ } });}Anti-patterns & best practices
Section titled “Anti-patterns & best practices”- Commit your corpus and reproducers. A crash input is a regression test; keep it so the fix never silently regresses.
- Fuzz the target, not the wrapper. Instrument the deepest parser, and let the harness only marshal bytes.
- Build with sanitizers. ASan/UBSan/TSan catch the memory and undefined-behaviour bugs that do not segfault on their own.
- Bound time and memory. Set
-timeout,-rss_limit_mband a run budget so one pathological input does not hang CI. - Treat any crash as a security bug first. Even a parser panic can be a denial of service; triage before dismissing.
- Run continuously, not once. Fuzzing rewards time; wire a short run into CI and a longer OSS-Fuzz job optionally.
Self-assessment quiz
Section titled “Self-assessment quiz”Self-assessment quiz
Pick one answer per question, then press Check answers.