Fuzz testing — or fuzzing — is an automated software testing technique that generates large volumes of unexpected, random, or malformed inputs and feeds them to a program to uncover crashes, memory errors, and security vulnerabilities. It was first developed by Barton Miller at the University of Wisconsin-Madison in 1988 and has since become one of the most effective tools in a security engineer's arsenal.
The premise is simple: programs have bugs at the edges of their input space — the inputs developers never thought to test. Fuzzing explores that space systematically, at machine speed, finding bugs that would take human testers months to discover.
How Does Fuzzing Work?
At its core, a fuzzer does three things in a tight loop:
- Generates input — either randomly, by mutating known-good seeds, or by constructing inputs from a grammar or model.
- Feeds that input to the target — by invoking it as a subprocess, calling a library function directly, or sending data over a socket.
- Monitors for interesting behavior — crashes, assertion failures, hangs, memory errors, or sanitizer violations (ASan, UBSan, MSan).
Modern fuzzers are far smarter than pure random input generation. Coverage-guided fuzzers like AFL++ and libFuzzer instrument the target binary at compile time to collect code coverage data, then use that feedback to steer mutation toward unexplored code paths. This approach is dramatically more effective at finding bugs deep in program logic.
What Bugs Does Fuzzing Find?
Fuzzing excels at uncovering bugs that are hard to find through manual review or unit testing:
- Memory safety bugs: heap buffer overflows, stack overflows, use-after-free, double-free, null pointer dereferences.
- Integer overflows and underflows: arithmetic that wraps unexpectedly at extreme values.
- Logic errors in parsers: malformed inputs that reach code paths the developer never anticipated.
- Denial-of-service conditions: inputs that cause infinite loops, excessive memory allocation, or exponential backtracking.
Google's OSS-Fuzz project has found over 10,000 bugs in open-source software since 2016 using continuous fuzzing infrastructure. Some of the most critical vulnerabilities in TLS libraries, image parsers, and browser engines were discovered by fuzzers.
Coverage-Guided Fuzzing: The State of the Art
Traditional random fuzzers hit a wall quickly with programs that have deep validation logic. Coverage-guided fuzzers solve this by instrumenting the binary at compile time to record which branches execute for each input, maintaining a corpus that covers as much code as possible, and preferentially mutating inputs that reach new or rare branches.
AFL, developed at Google, pioneered this approach. AFL++ extends it with improved mutation strategies and support for black-box targets via QEMU. libFuzzer uses the same feedback loop but runs in-process, eliminating subprocess overhead and achieving higher execution rates.
Writing a Fuzz Target
For libFuzzer and AFL++, you write a fuzz harness — a small function that accepts a byte buffer and passes it to the code under test:
#include <stdint.h>
#include <stddef.h>
#include "my_parser.h"
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
parse_input(data, size);
return 0;
}Compile with AddressSanitizer (-fsanitize=address,undefined) so memory errors are caught immediately rather than manifesting as unexplained corruption later.
Does Fuzzing Replace Unit Tests?
Fuzzing and unit testing are complementary. Unit tests cover inputs you thought of; fuzzing covers inputs you didn't. A fuzzer generates millions of inputs per second. A human test writer generates hundreds per year. Fuzzing also finds emergent behavior at component boundaries that isolated unit tests miss entirely.
Microsoft's Security Development Lifecycle mandates fuzzing before shipping. Apple, Google, Mozilla, and virtually every major software company run continuous fuzzing infrastructure.
Getting Started
The main barrier to adopting fuzzing has historically been infrastructure — setting up a cluster, managing workers, collecting crashes, and deduplicating results can take weeks before you fuzz a single line of code. Managed fuzzing platforms eliminate that overhead: you provide a compiled harness binary and the platform handles the rest.