Comprehensive Fuzzing Guide
🆕 Enhanced May 2, 2026 - Updated with AI-augmented fuzzing techniques, JVM fuzzing via Jazzer, Kotlin coroutine testing, advanced coverage methods, and modern language support from comprehensive 2026 fuzzing research analysis.
A practitioner’s reference for fuzz testing — fundamentals, coverage feedback, harness construction, corpus strategy, sanitizer usage, and the tool stack for web, binary, kernel, API, and smart-contract targets. Compiled from 46 research sources.
Table of Contents
- Fundamentals
- Fuzzing Taxonomy
- Coverage-Guided Fuzzing
- Harness Construction
- Corpus Management & Seed Selection
- Dictionaries & Structure-Aware Fuzzing
- Sanitizers
- Binary Fuzzing (AFL++, libFuzzer, honggfuzz, LibAFL)
- Web Fuzzing (ffuf, wfuzz, feroxbuster, Burp Intruder)
- API Fuzzing (REST, GraphQL, Protobuf)
- Kernel & OS Fuzzing
- Directed & Grammar-Based Fuzzing
- AI-Augmented Fuzzing
- JVM Fuzzing (Jazzer, LibAFL)
- Rust & Python Fuzzing
- Snapshot Fuzzing (Nyx, HyperHook)
- Smart Contract Fuzzing
- Protocol & Network Fuzzing (Boofuzz, ICS)
- Crash Triage & Minimization
- CI/CD Integration
- Bugs That Survive Continuous Fuzzing
- Real-World Wins & CVEs
- Tools & Frameworks Reference
- Wordlist & Corpus Resources
- Quick Reference Cheatsheet
1. Fundamentals
Fuzzing is automated software testing by bombarding a target with a large volume of semi-random, invalid, or unexpected inputs and watching for crashes, hangs, memory errors, or assertion failures. The technique originates with Barton Miller’s 1988 University of Wisconsin-Madison experiment, where random inputs crashed roughly a third of tested Unix utilities.
The core loop:
- Test case generation — synthesize or mutate inputs.
- Test execution — run the target with the input.
- Monitoring — observe crashes, hangs, sanitizer reports, coverage.
- Feedback — prioritize interesting inputs, discard redundant ones.
- Crash analysis — deduplicate, minimize, and root-cause the finding.
Why fuzzing works: It surfaces real execution failures — segfaults, UAF, OOB reads/writes, integer overflows, assertion violations — not theoretical bugs. Unlike static analysis, there are few false positives: if the fuzzer crashed the target, the target crashed.
Ideal targets:
| Category | Examples |
|---|---|
| File parsers | PDF, PNG, JPEG, TIFF, audio/video codecs |
| Network protocols | HTTP, DNS, TLS, QUIC, Bluetooth stacks, Netlink |
| Language runtimes | JavaScript engines, WASM, regex engines |
| Serialization | Protobuf, msgpack, CBOR, ASN.1, BSON |
| Crypto libraries | OpenSSL, BoringSSL, NSS |
| OS kernel surfaces | syscalls, ioctls, filesystem drivers, USB stack |
| Web APIs | REST, GraphQL, gRPC endpoints |
| Databases | SQL parsers, NoSQL query engines |
A good target processes external, attacker-controllable input, has parsing logic, uses low-level memory primitives, or implements complex state machines.
2. Fuzzing Taxonomy
By input generation strategy
| Type | Starts with | Best for | Tools |
|---|---|---|---|
| Mutation-based | Valid sample inputs; flips bits, inserts/deletes bytes, splices | Binary formats, legacy CLIs, when you have a corpus | AFL++, honggfuzz, Radamsa |
| Generation-based | A grammar, model, or protocol spec | Structured inputs (JS, HTML, JSON, protocols) | Peach, BooFuzz, SPIKE, Fuzzilli, Domato |
| Hybrid | Both; grammar seeds feed into mutation engine | Language runtimes, parsers | Fuzzilli, Dharma |
By visibility into the target
| Type | Knowledge | Strength | Weakness |
|---|---|---|---|
| Black-box | None — I/O only | Easy to set up, no build changes | Shallow coverage, misses deep paths |
| Grey-box | Lightweight instrumentation (edge coverage) | Best balance of effort and results — the modern default | Needs recompilation or binary rewriting |
| White-box | Full source + symbolic/concolic execution | Reaches deep constraints | Expensive, brittle, complex tooling |
When to use black-box vs coverage-guided (per ClusterFuzz)
Coverage-guided works best when:
- Target is self-contained and deterministic.
- Can run hundreds of executions per second.
- Classic example: binary format parsers.
Black-box is preferred when:
- Target is large and slow (full browsers).
- Nondeterministic across runs for the same input.
- Input grammar is extremely structured (JavaScript, HTML DOM).
Differential fuzzing
Feed the same input to multiple implementations of the same spec and flag divergences. Excellent for:
- Cross-browser parser comparison (HTML, CSS, JSON)
- Crypto library consistency (Project Wycheproof)
- Language spec compliance (LangFuzz)
3. Coverage-Guided Fuzzing
Coverage-guided (grey-box) fuzzing is the modern default. The fuzzer instruments the target at compile time so every edge (branch transition in the CFG) reports into a shared bitmap. Inputs that reach new edges are kept and mutated; inputs that only retrace existing coverage are discarded.
The feedback loop (AFL / libFuzzer)
- Pick the most promising test case from the queue.
- Mutate it into many children (bit flips, arithmetic, splicing, havoc).
- Run each child; the instrumented binary updates the coverage bitmap.
- Score each child by new coverage. Promising ones enter the corpus.
- Repeat.
AFL’s coverage bitmap
AFL allocates a 64K 8-bit array called trace_bits/shared_mem. Each cell is a hit counter for a (branch_src, branch_dst) tuple. Instrumentation pseudocode:
cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;
The shift-by-one on prev_location preserves directionality (A→B is distinct from B→A).
Clang’s SanitizerCoverage
libFuzzer and AFL++ both rely on Clang’s -fsanitize-coverage= instrumentation. Compile with:
clang -fsanitize=address,fuzzer fuzzer.cc -o fuzzer
## Or for AFL++ compatibility:
clang -fsanitize=address -fsanitize-coverage=trace-pc-guard target.c -o target
The runtime callbacks __sanitizer_cov_trace_pc_guard, __sanitizer_cov_trace_cmp*, and __sanitizer_cov_trace_switch let the engine record edges, compare operands (CMPLOG/COMPCOV), and switch-table legs.
Extending instrumentation
You can hook __sanitizer_cov_trace_pc_guard to capture more than edge hits — for example, the return address via __builtin_return_address(0) to drive directed fuzzing toward known-dangerous functions:
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
void *PC = __builtin_return_address(0);
char desc[1024];
__sanitizer_symbolize_pc(PC, "%p %F %L", desc, sizeof(desc));
// compare PC against a watchlist of vulnerable functions
__shmem->edges[*guard / 8] |= 1 << (*guard % 8);
}
This technique (demonstrated on Fuzzilli + JerryScript) lets the fuzzer prioritize inputs that reach historically buggy files or functions.
Coverage metrics: not all edges are equal
Research (NDSS “Not All Coverage Measurements Are Equal”) shows that weighting edges by security impact — e.g., edges inside memory allocators, string handlers, or unsafe sinks — outperforms flat edge counting. GRLFuzz goes further and uses reinforcement learning to pick mutation strategies per seed based on historical reward.
Context-sensitive coverage
Standard edge coverage does not track execution order. Different calling sequences can produce identical edge bitmaps while reaching very different program states. AFL++ addresses this with two options:
- Context-sensitive branch coverage — each function gets a unique ID; the fuzzer hashes the call stack IDs together with the edge identifier, so the same edge reached through different call paths counts as distinct coverage.
- N-Gram branch coverage — combines the current location with the previous N locations (1-gram, 2-gram, 4-gram). Higher N values distinguish more execution orderings but increase bitmap pressure.
Context-sensitive coverage targets above 60% are considered strong (unlike the 90%+ achievable with flat edge coverage), because the state space is combinatorially larger.
Value coverage and advanced instrumentation (2026)
Even 100% edge coverage can miss bugs that depend on specific variable values. A division-by-zero triggered only when r.padding == 4312 will survive millions of edge-guided iterations if no input happens to produce that value.
Value coverage techniques:
Value coverage tracks which value ranges a variable takes across executions. By inserting binary-search-like branch trees that map variable values into distinct edges, the fuzzer’s coverage feedback naturally steers toward unexplored value regions.
// Automatic value coverage instrumentation (2026)
void instrument_value_coverage(int value, int id) {
// Map value to coverage bitmap using binary search tree
if (value < 100) {
if (value < 50) __coverage_map[id * 8 + 0] = 1;
else __coverage_map[id * 8 + 1] = 1;
} else {
if (value < 1000) __coverage_map[id * 8 + 2] = 1;
else __coverage_map[id * 8 + 3] = 1;
}
// Track value histogram for AI-guided mutation
__value_histogram[id][value % 256]++;
}
// Call at interesting variable assignments
int padding = parse_padding(input);
instrument_value_coverage(padding, PADDING_VALUE_ID);
if (padding == 0) { /* division by zero path */ }
Taint tracking coverage (2026):
Modern fuzzers track data flow from input bytes to program variables to guide mutations more precisely:
// Taint-guided coverage with byte-level tracking
void track_input_influence(void *ptr, size_t size, uint32_t input_offset) {
// Map which input bytes influence which program variables
TaintMetadata *taint = get_taint_info(ptr);
taint->input_offset = input_offset;
taint->length = size;
// When this value participates in control flow,
// bias mutations toward the influencing input bytes
register_control_flow_influence(ptr, taint);
}
Control-flow integrity (CFI) coverage:
LibAFL and modern AFL++ variants can instrument indirect calls to detect control-flow hijacking attempts:
// CFI instrumentation for fuzzing
void __sanitizer_cov_trace_pc_indirect(void *callee) {
uintptr_t caller = (uintptr_t)__builtin_return_address(0);
uintptr_t target = (uintptr_t)callee;
// Track indirect call targets for CFI violations
uint32_t hash = hash_pair(caller, target);
__cfi_bitmap[hash % CFI_MAP_SIZE] = 1;
// Flag unexpected call targets
if (!is_valid_call_target(caller, target)) {
__sanitizer_set_death_callback(cfi_violation_handler);
abort();
}
}
This technique extends standard coverage-guided fuzzing to catch arithmetic bugs, boundary conditions, magic-value-dependent paths, and control-flow violations that flat edge coverage misses.
4. Harness Construction
A harness (or fuzz target) is a small wrapper that hands fuzzer-provided bytes to the code you actually want to test. The libFuzzer-style entry point is the de facto standard, understood by libFuzzer, AFL++, honggfuzz, and Centipede:
// fuzz_target.c
#include <stdint.h>
#include <stddef.h>
extern int parse_thing(const uint8_t *data, size_t len);
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 4) return 0;
parse_thing(data, size);
return 0;
}
Build it:
clang -g -O1 -fsanitize=fuzzer,address fuzz_target.c parser.c -o fuzz_target
./fuzz_target corpus/ -max_len=4096
Harness design rules
- Keep it fast. Aim for thousands of execs/sec. Every millisecond of setup costs orders of magnitude in total coverage.
- Stateless where possible. Reset global state between inputs; if not possible, use
-runs=1or fork mode. - Exercise realistic entry points. Wrap the same functions an attacker can reach — not helper internals.
- Split the input. For multi-argument APIs, carve
datainto pieces with a small prefix header orFuzzedDataProvider. - Avoid nondeterminism. Seed any RNG with a constant; disable timestamps, thread scheduling surprises.
- Check assertions, not output. Let sanitizers do the talking.
- Limit allocations. Cap input size (
-max_len=) to avoid OOM noise.
FuzzedDataProvider (libFuzzer helper)
#include <fuzzer/FuzzedDataProvider.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
FuzzedDataProvider fdp(data, size);
int mode = fdp.ConsumeIntegralInRange<int>(0, 3);
std::string name = fdp.ConsumeRandomLengthString(64);
auto rest = fdp.ConsumeRemainingBytes<uint8_t>();
target_api(mode, name.c_str(), rest.data(), rest.size());
return 0;
}
Harness scope: narrow vs broad
A common design decision is how much code the harness exercises. Narrow harnesses (one parser, one function) are fast to write and yield high coverage for isolated components but miss integration-level bugs. Broad harnesses (entire protocol stacks, full API surfaces) give the fuzzer a huge search space, causing it to spend months reaching moderate coverage. The practical sweet spot: one harness per logical subsystem, targeting the same entry points an attacker can reach.
Multi-language harness patterns
Rust (cargo-fuzz / cargo-libafl):
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = my_crate::parse(s);
}
});
Go (native testing.F):
func FuzzParse(f *testing.F) {
f.Add([]byte("seed"))
f.Fuzz(func(t *testing.T, data []byte) {
_, _ = Parse(data)
})
}
Common harness anti-patterns
- Calling
exit()orabort()on invalid input — the fuzzer sees these as crashes. - Reading from a file path inside the harness — slow and non-hermetic.
- Leaking memory every call — ASan will flag each run as a “crash.”
- Catching all exceptions and returning silently — hides real bugs.
- Writing to global state that isn’t reset — causes flaky reproducers.
- Reusing input bytes for multiple purposes — e.g., using the same bytes for a control decision and as payload data. This creates conflicting mutation pressure. Use
FuzzedDataProviderto consume bytes independently. - Reinterpreting input — casting the same buffer to different types in different branches. Each interpretation competes for the fuzzer’s mutation energy.
5. Corpus Management & Seed Selection
A corpus is the set of inputs the fuzzer has deemed “interesting” (reaches unique coverage). Seed corpus is the starting material you hand it.
Seed selection principles
- Diversity over volume. 50 structurally different PDFs outperform 5,000 near-duplicate PDFs.
- Small is beautiful. Tiny seeds mutate faster and cover more ground. Aim for <1 KB where possible.
- Harvest real inputs. Pull samples from your test suite, public corpora, or real network captures.
- Include pathological cases. Empty files, single bytes, maximum-size inputs, boundary values.
Corpus pruning (minimization)
Over time the corpus grows unbounded. Pruning keeps only inputs that uniquely cover at least one edge. ClusterFuzz runs CORPUS_PRUNE = True once a day. Locally:
## libFuzzer: merge old corpus into a minimal new one
./fuzz_target -merge=1 corpus_min/ corpus/
## AFL: cmin for corpus, tmin for individual inputs
afl-cmin -i corpus -o corpus_min -- ./target @@
afl-tmin -i crash_input -o crash_min -- ./target @@
Seed corpus conventions
Tools like OSS-Fuzz and ClusterFuzz expect zipped corpora named <fuzz_target>_seed_corpus.zip placed alongside the binary. Dictionaries go in <fuzz_target>.dict.
Public corpus sources
- oss-fuzz corpora — public backups of Google OSS-Fuzz targets
- Fuzzer Test Suite — Google’s historical benchmark seeds
- Mozilla fuzzdata — browser-relevant formats
- DARPA CGC — challenge binaries with seeds
- VirusTotal / Malware Bazaar — real-world file samples (handle with care)
6. Dictionaries & Structure-Aware Fuzzing
Pure byte-level mutation struggles with formats that have magic numbers, keywords, or long tokens. A dictionary is a newline-separated list of interesting byte strings the mutator can splice in.
Dictionary format (libFuzzer / AFL)
## A comment
"FILE"
"\xff\xd8\xff\xe0"
"JFIF\x00"
kw_function="function"
kw_return="return"
Pass to libFuzzer with -dict=keywords.dict or drop it alongside the target as <target>.dict.
Where dictionaries help the most
- Language grammars —
function,return,=>,async - Binary magic bytes — PNG
\x89PNG, ELF\x7fELF, PDF%PDF- - HTTP verbs, headers —
GET,POST,Content-Type: - SQL keywords —
SELECT,UNION,WHERE - Protocol framing bytes
Structure-aware fuzzing
For inputs where structural validity matters (JavaScript, SQL, protobuf, HTTP/2), pure mutation is too destructive. Options:
| Technique | Description | Tools |
|---|---|---|
| Grammar-based generation | Produce inputs from a BNF/EBNF | Dharma, Domato, Grammarinator |
| Intermediate language (IL) mutation | Fuzz an AST/IR, then lower to bytes | Fuzzilli (JS), Token-level fuzzers |
| libprotobuf-mutator | Mutate serialized protobuf messages preserving schema | LPM + libFuzzer |
| Custom mutators | libFuzzer’s LLVMFuzzerCustomMutator hook | Any engine |
| Splicing | Combine fragments from valid corpus entries | Built into AFL++ |
Fuzzilli, for example, generates FuzzIL (its own typed IR for JavaScript), mutates at the IR level, then lowers to JS source — ensuring outputs are mostly syntactically valid and much more semantically meaningful than byte flips on a .js file.
7. Sanitizers
Sanitizers are Clang/GCC-provided compile-time instrumentation that turn latent memory/undefined-behavior bugs into loud, debuggable crashes. Without a sanitizer, many bugs corrupt memory silently and only crash later — if at all. Always fuzz under a sanitizer.
| Sanitizer | Flag | Detects |
|---|---|---|
| ASan (AddressSanitizer) | -fsanitize=address | Heap/stack/global buffer overflows, UAF, double-free, memory leaks (via LSan) |
| UBSan (UndefinedBehaviorSanitizer) | -fsanitize=undefined | Signed integer overflow, NULL deref, misaligned access, divide-by-zero, OOB shifts |
| MSan (MemorySanitizer) | -fsanitize=memory | Use of uninitialized memory — all transitive deps must also be MSan-built |
| TSan (ThreadSanitizer) | -fsanitize=thread | Data races, deadlocks |
| LSan (LeakSanitizer) | -fsanitize=leak | Memory leaks (bundled into ASan by default) |
| CFI (Control Flow Integrity) | -fsanitize=cfi | Indirect call hijacking |
Typical build incantation
## libFuzzer + ASan + UBSan combo
clang++ -g -O1 \
-fsanitize=fuzzer,address,undefined \
-fno-sanitize-recover=all \
-fno-omit-frame-pointer \
fuzz_target.cc target.cc -o fuzz_target
Advanced sanitizer integration (2026)
Modern sanitizer combinations:
## Production fuzzing build (2026 recommended)
clang++ -g -O1 \
-fsanitize=address,undefined,bounds,nullability \
-fsanitize-address-use-after-scope \
-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div,trace-gep \
-fno-sanitize-recover=all \
-fno-omit-frame-pointer \
-fstack-protector-strong
## Advanced memory tracking
clang++ -fsanitize=memory \
-fsanitize-memory-track-origins=2 \
-fsanitize-memory-use-after-dtor
## Hardware-assisted detection (Intel CET)
clang++ -fsanitize=address \
-fcf-protection=full \
-mshstk # Intel Shadow Stack
Custom application-level sanitizers (2026):
// Domain-specific vulnerability detection
void __attribute__((no_sanitize("address")))
detect_injection_vulnerability(const char *input) {
// SQL injection patterns
if (strstr(input, "UNION SELECT") ||
strstr(input, "'; DROP TABLE") ||
strstr(input, "' OR '1'='1")) {
__sanitizer_print_stack_trace();
abort(); // Flag as finding
}
// Command injection patterns
if (strstr(input, "$(") || strstr(input, "`") ||
strstr(input, "| nc ") || strstr(input, "&& rm")) {
fprintf(stderr, "Command injection detected: %s\n", input);
abort();
}
// Path traversal
if (strstr(input, "../") && strstr(input, "/etc/passwd")) {
fprintf(stderr, "Path traversal detected: %s\n", input);
abort();
}
}
// Integrate into target code
void process_user_input(const char *input) {
detect_injection_vulnerability(input); // Custom sanitizer
// ... normal processing
}
Sanitizer performance optimization:
## Fast fuzzing build (reduced overhead)
-fsanitize=address -fsanitize-address-use-odr-indicator \
-mllvm -asan-instrument-reads=false # Skip read instrumentation for speed
-mllvm -asan-instrument-atomics=false
## Sampling-based detection (lower overhead)
-fsanitize=address -mllvm -asan-instrument-dynamic-allocas=false \
-fsanitize-coverage=trace-pc-guard # Only coverage, no comparisons
Sanitizer pitfalls and modern solutions
Traditional challenges:
- MSan requires all linked libraries to also be MSan-built, or you’ll drown in false positives.
- ASan roughly doubles memory usage and slows execution ~2x — worth it.
- UBSan defaults to warnings; pair with
-fno-sanitize-recover=allto make them fatal. - Don’t mix ASan and MSan in the same binary (they conflict).
2026 improvements:
- Partial MSan builds — use
-fsanitize-ignorelistto exclude problematic libraries - ASan performance modes — sampling and selective instrumentation reduce overhead
- Hardware acceleration — Intel MPX (deprecated) replaced with Intel CET for control flow
- Cloud-native sanitizers — containerized fuzzing with sanitizer-optimized images
- Sanitizer fusion — tools like HWASan combine software and hardware approaches
OSS-Fuzz evolution (2026):
Each target now built with 5+ configurations:
- ASan+UBSan (standard memory errors)
- MSan (uninitialized memory)
- TSan (data races)
- Custom domain sanitizers (SQL injection, XSS, etc.)
- Hardware-assisted variants where available
Kernel sanitizers and system-level fuzzing
The Linux kernel has its own family: KASAN (address), KMSAN (uninit memory), UBSAN, KCSAN (concurrency), KFENCE (low-overhead memory error detection). Syzkaller enables these by default.
Modern kernel sanitization (2026):
## Enhanced KASAN with stack and globals
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_STACK=y
CONFIG_KASAN_VMALLOC=y
## Kernel control flow integrity
CONFIG_CFI_CLANG=y
CONFIG_CFI_PERMISSIVE=n
## Hardware-assisted kernel fuzzing
CONFIG_INTEL_IOMMU=y
CONFIG_INTEL_IOMMU_DEBUGFS=y # For DMA attack surface fuzzing
8. Binary Fuzzing (AFL++, libFuzzer, honggfuzz, LibAFL)
AFL / AFL++
AFL (American Fuzzy Lop), written by Michał Zalewski, pioneered practical coverage-guided fuzzing. AFL++ is the community fork with advanced features: CMPLOG (magic-value solving), COMPCOV (byte-compare splitting), QEMU and Unicorn modes for blackbox binaries, LAF-INTEL transformations, persistent mode, and collision-free coverage.
Install and run:
sudo apt-get install -y afl++ clang llvm
## Compile with AFL's wrapper
AFL_USE_ASAN=1 afl-clang-fast -o target target.c
## Fuzz it
afl-fuzz -i input_corpus -o findings -- ./target @@
The @@ token is replaced by AFL with the path to each generated test case.
AFL++ power features
| Feature | Purpose |
|---|---|
| CMPLOG | Logs comparison operands so the mutator can solve magic-byte checks |
| LAF-INTEL | Splits multi-byte comparisons into per-byte branches so coverage sees partial progress |
| Persistent mode | Loops the harness N times per fork to amortize startup cost (huge speedup) |
| QEMU mode | Instrumentation-free fuzzing of closed-source binaries |
| FRIDA mode | Dynamic instrumentation for binaries on macOS/Android |
| Nyx | Snapshot-based full-system fuzzing via KVM |
libFuzzer
libFuzzer is LLVM’s in-process, coverage-guided fuzzer. It lives inside your harness binary — no fork-exec per input — making it the fastest option for library fuzzing.
// fuzz_parser.cc
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
return parse(data, size), 0;
}
clang++ -g -fsanitize=fuzzer,address fuzz_parser.cc parser.cc -o fuzz
./fuzz corpus/ -max_len=4096 -dict=keywords.dict -jobs=8 -workers=8
Key libFuzzer flags:
| Flag | Purpose |
|---|---|
-max_len=N | Cap input length |
-dict=file | Use a dictionary |
-jobs=N -workers=N | Parallel fuzzing processes |
-merge=1 dst src | Merge/minimize corpora |
-runs=N | Run N iterations then exit (CI mode) |
-timeout=N | Per-input timeout in seconds |
-rss_limit_mb=N | Memory cap |
-fork=N | Run N child processes for crash isolation |
honggfuzz
Robert Swiecki’s honggfuzz supports both feedback-driven and dumb fuzzing, hardware-assisted coverage (Intel PT/BTS), and persistent mode. It has an excellent reputation for finding bugs in crypto libraries and was used for many OpenSSL/BoringSSL discoveries.
honggfuzz -i corpus -- ./target ___FILE___
LibAFL
LibAFL is a modular fuzzing library written in Rust by the AFL++ team. Unlike monolithic fuzzers, LibAFL provides composable building blocks — observers, feedback, schedulers, executors, mutators — that you assemble into a custom fuzzer. This makes it the tool of choice when you need behavior that no off-the-shelf fuzzer supports.
Two ways to use LibAFL:
- libFuzzer drop-in replacement — compile with
libafl_cc(or the libfuzzer-compatibility layer) and run existingLLVMFuzzerTestOneInputharnesses without code changes. Requires nightly Rust. - Custom Rust fuzzer — write a Rust binary that uses LibAFL’s crates to wire up your own feedback loop, mutator pipeline, and executor.
LibAFL’s key advantages over libFuzzer:
| Feature | LibAFL | libFuzzer |
|---|---|---|
| Active development | Yes | Maintenance mode (Google shifted to Centipede) |
| Custom feedback | Arbitrary feedback types via traits | Fixed edge-coverage model |
| Distributed fuzzing | Built-in multi-node support | Manual via -fork |
| Snapshot support | Nyx integration via libafl_nyx | None |
| Language support | C/C++, Rust, Java (via Jazzer fork), Python | C/C++ only |
## LibAFL as libFuzzer drop-in (after building libafl_libfuzzer)
cargo build --release -p libafl_libfuzzer
clang -fsanitize=address -g target.c \
-L target/release -l afl_libfuzzer -o fuzz_target
./fuzz_target corpus/
WinAFL
For Windows targets, WinAFL uses DynamoRIO or Intel PT to provide coverage feedback on closed-source binaries:
winafl-fuzz.exe -i in -o out -D path\to\dynamorio\bin64 \
-t 10000 -- -coverage_module target.dll -target_module target.exe \
-target_offset 0x1234 -fuzz_iterations 5000 -nargs 1 -- target.exe @@
target_offset is the RVA of the function you want persistent-looped.
Directed greybox fuzzing on Windows
Directed fuzzers (AFLGo, Hawkeye, and Windows-specific ports) combine coverage guidance with distance metrics — how close each input gets to a target site in the CFG. Useful for patch testing, reproducing known CVEs, and hunting variants near a known-vulnerable function.
9. Web Fuzzing (ffuf, wfuzz, feroxbuster, Burp Intruder)
Web fuzzing is less about memory corruption and more about content discovery (hidden endpoints, backup files, parameter names) and input probing (SQLi, XSS, SSRF, path traversal payloads).
ffuf
Fast, Go-based, the modern default.
## Directory discovery
ffuf -u https://target.com/FUZZ -w raft-medium-directories.txt -t 50
## Subdomain discovery
ffuf -u https://FUZZ.target.com -w subdomains-top1million.txt -H "Host: FUZZ.target.com"
## Parameter discovery
ffuf -u "https://target.com/api?FUZZ=test" -w params.txt -fs 1234
## POST body fuzzing
ffuf -u https://target.com/login -X POST \
-d "username=admin&password=FUZZ" -w rockyou.txt \
-H "Content-Type: application/x-www-form-urlencoded" -mc 200,302
## JSON body
ffuf -u https://target.com/api/v1/users -X POST \
-d '{"name":"FUZZ"}' -H "Content-Type: application/json" -w names.txt
Filter flags (-fc, -fs, -fw, -fl) are essential for noisy targets with custom 404s:
ffuf -u https://target.com/FUZZ -w words.txt -fc 404,403 -fs 1337
feroxbuster
Rust-based, recursive by default, great for deep directory trees:
feroxbuster -u https://target.com -w raft-medium-directories.txt -x php,bak,zip -d 3
wfuzz
Older Python tool, still useful for its multi-injection-point syntax and filter language:
wfuzz -c -w users.txt -w pass.txt --hc 401 \
-d "user=FUZZ&pass=FUZ2Z" https://target.com/login
Burp Suite Intruder
Four attack modes:
| Mode | Use case |
|---|---|
| Sniper | One payload list, one marker at a time — classic fuzz |
| Battering Ram | Same payload into every marker simultaneously |
| Pitchfork | Parallel payload sets, walked in lockstep |
| Cluster Bomb | Cartesian product of payload sets (credential spraying) |
Mark insertion points with §, pick a payload list, hit Start. Community Edition throttles Intruder heavily; Pro is effectively required for serious engagements.
Burp Collaborator
For blind/out-of-band bugs (blind SSRF, blind XXE, blind command injection, blind SQLi), Collaborator provides DNS+HTTP+SMTP callback endpoints. Inject http://<random>.burpcollaborator.net and watch for hits.
Web fuzzing targets that matter
- Hidden endpoints (
/admin,/.git/config,/backup.zip,/api/v2/internal) - HTTP parameter names (
debug=1,admin=true, internal flags) - Header values (
X-Forwarded-For,X-Original-URL,Host) - Cookie values and session tokens
- File upload content-type / extension allowlists
- Race condition windows (burst-parallel Intruder or Turbo Intruder)
10. API Fuzzing (REST, GraphQL, Protobuf)
Modern apps expose most of their attack surface through APIs, so API fuzzing has become its own subdiscipline. It differs from web content fuzzing in that the inputs are structured (JSON, XML, protobuf) and the API contract (OpenAPI, GraphQL schema, proto files) can drive generation.
REST API fuzzers
| Tool | Approach | Notes |
|---|---|---|
| EvoMaster | Search-based white-box + black-box | Generates JUnit tests; used in production at Volkswagen AG |
| RESTler | Stateful, infers dependencies between endpoints | Microsoft Research |
| Schemathesis | Property-based, OpenAPI/Swagger-driven | Python, CI-friendly |
| Dredd | Contract testing against OpenAPI | Pass/fail per endpoint |
| bBOXRT | Black-box robustness testing | Academic |
| Morest / ARAT-RL / AutoRestTest | RL and model-based | Recent academic tools |
Search-based REST fuzzing (EvoMaster)
EvoMaster treats test generation as a search problem, using evolutionary algorithms to maximize coverage over time. The Volkswagen AG industrial study (2023-2026) surfaced several practical requirements for fuzzers to be usable outside academic labs:
- Authentication chaining — login flows that produce tokens used by later calls.
- Dependency inference —
POST /usersreturns an id used byGET /users/{id}. - External system mocking — black-box mode where downstream SaaS can’t be hammered.
- Stable, idempotent reruns — tests must survive DB state changes.
- Readable, maintainable generated tests — engineers need to understand what failed and why.
- Oracle beyond 5xx — business-logic violations without a crash.
GraphQL fuzzing
GraphQL’s introspection query (__schema) gives you a full type graph for free, making grammar-based fuzzing straightforward. Notable tools:
- InQL (Burp plugin) — extracts operations from introspection, generates request templates
- clairvoyance — infers schema even when introspection is disabled
- graphql-cop — lightweight misconfig scanner
- GraphQLmap — schema-driven fuzzer
Common GraphQL fuzz targets: batching DoS, field duplication DoS, alias-based rate-limit bypass, introspection leaks, SQLi/NoSQLi in resolvers, IDOR via object-level authorization gaps.
Protobuf / gRPC fuzzing
Protobuf schemas give you a perfect generator. Use libprotobuf-mutator with libFuzzer to mutate typed messages:
#include "src/libfuzzer/libfuzzer_macro.h"
#include "my_message.pb.h"
DEFINE_PROTO_FUZZER(const my::Message &msg) {
handle_message(msg);
}
LPM keeps messages schema-valid while mutating individual fields — far more effective than flipping bits in a serialized blob.
11. Kernel & OS Fuzzing
Kernel fuzzing is harder than userspace: the target is stateful, crashes require VM reboots, and coverage has to cross the syscall boundary.
syzkaller (syzbot)
Dmitry Vyukov’s syzkaller is the state of the art for Linux (and FreeBSD, NetBSD, OpenBSD, Fuchsia, Windows) kernel fuzzing. It:
- Generates syscall sequences from a declarative description language (
.txtdescriptions). - Uses KCOV for coverage feedback.
- Runs many VMs in parallel, snapshotting and rebooting on crashes.
- Automatically bisects kernel commits to find the introducing change.
- Syzbot files bugs upstream with reproducers attached.
Syzkaller has found hundreds of Linux kernel vulnerabilities and is responsible for a substantial fraction of all kernel CVEs in recent years.
KCOV (Linux kernel coverage)
Compile the kernel with CONFIG_KCOV=y and (selectively) KCOV_INSTRUMENT := y in the Makefiles of subsystems you care about. From userspace:
int fd = open("/sys/kernel/debug/kcov", O_RDWR);
ioctl(fd, KCOV_INIT_TRACE, COVER_SIZE);
unsigned long *cover = mmap(NULL, COVER_SIZE * sizeof(unsigned long),
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
ioctl(fd, KCOV_ENABLE, KCOV_TRACE_PC);
// ... run the code you want to profile ...
ioctl(fd, KCOV_DISABLE, 0);
// cover[0] holds count, cover[1..] are %rip values of basic blocks
KCOV coverage is per-task and ring-buffer-based. Combined with KASAN, it turns any kernel subsystem into a fuzzable target.
A custom AFL+KCOV setup
You can trick AFL into thinking your harness is instrumented by having it fake the trace_bits shared memory: fork from AFL’s forkserver protocol, call your target code while KCOV is enabled, then hash KCOV %rip values into the AFL bitmap before reporting completion. The Cloudflare blog post (“A gentle introduction to Linux Kernel fuzzing”) walks through a netlink fuzzer built this way — you build a KCOV-enabled kernel, run it in virtme/KVM, and expose a shim that AFL drives.
Other kernel fuzzers
| Tool | Target | Notes |
|---|---|---|
| Trinity | Linux syscalls | Classic, argument-aware but not coverage-guided |
| kAFL | Full kernels via Intel PT | Hypervisor-assisted |
| Syzkaller | Linux + others | The workhorse |
| Nyx | Snapshot-fuzzing full VMs | Extremely fast |
| Digtool | Windows kernel | Academic |
External network fuzzing with syzkaller
Syzkaller can be extended to fuzz the kernel’s network stack externally — injecting raw packets via TUN/TAP and collecting coverage via KCOV. The approach:
- Create a TUN device in the fuzzer VM to inject packets directly into the kernel’s network stack.
- Enable KCOV with
KCOV_REMOTEto collect coverage from softirq/network processing contexts (not just the syscall thread). - Define syzkaller “pseudo-syscalls” that wrap packet injection as callable operations, with structured descriptions of IP/TCP/UDP/ICMP headers.
- Handle checksums — syzkaller must compute valid IP/TCP checksums or the kernel drops packets before reaching interesting code.
- Establish TCP connections by implementing a minimal TCP handshake in the fuzzer to reach connection-state-dependent code paths.
This technique found multiple remotely-triggerable bugs in the Linux kernel, including a one-shot RCE in a non-public kernel flavor. The approach surfaces bugs in protocol parsers that traditional syscall-based fuzzing cannot reach because those code paths are only exercised by incoming network packets.
False positives in kernel fuzzing
Not every syzkaller crash is a real bug. A documented class of false positives involves soft lockup warnings in network scheduler (net/sched) fuzzing. These occur when qdisc parameters like quantum=1 combined with large stab overhead values cause the dequeue loop to spin for tens of seconds — long enough to trigger the soft lockup watchdog, but not an actual hang. The root cause is that syzkaller’s executor does not fully reset network namespaces between runs (for performance), so qdisc modifications from previous programs persist and affect subsequent executions.
Diagnosing these requires manual bisection of syzkaller logs, re-running with syz-execprog, and dumping tc state via nsenter into the executor’s network namespace. These are not security bugs but waste triage time.
Bugs in kernel fuzzing are tricky
Most netlink/syscall bugs don’t have direct security impact because the interface requires privilege, but UAFs, stack OOBs, and race conditions in filesystem/networking code frequently become LPEs. Always pair kernel fuzzing with KASAN + UBSAN + KMSAN.
12. Directed & Grammar-Based Fuzzing
Directed greybox fuzzing (DGF)
Standard coverage-guided fuzzers try to cover everything. Directed fuzzers focus effort on specific target sites in the CFG — useful for:
- Patch testing (does my fix hold?)
- CVE reproduction and variant hunting
- Reaching a specific function under a complex path condition
AFLGo assigns each basic block a distance to the target and uses simulated annealing over that distance as the fitness function. Variants include Hawkeye (function-level distance), 1dFuzz (for 1-day patch testing), and directed Windows fuzzers built on WinAFL.
Grammar-based fuzzing
Pure byte mutation is terrible at generating valid JavaScript, SQL, or HTML. Grammar-based approaches encode the input language and generate valid (or almost-valid) programs:
| Tool | Language | Notes |
|---|---|---|
| Fuzzilli | JavaScript | IL-based; found dozens of V8/JSC/SpiderMonkey bugs |
| Domato | HTML/CSS/JS DOM | Chrome security team |
| Dharma | Any grammar | Mozilla, generation-only |
| Grammarinator | ANTLR grammars | Covers many real-world languages |
| Superion | Structure-aware AFL++ | Injects tree mutations |
| Nautilus | Grammar + coverage feedback | Strong on interpreters |
Hybrid: concolic / symbolic execution
When mutation stalls at a hard branch (e.g., if (input == 0xdeadbeef)), symbolic execution can solve the constraint. Driller (AFL + angr) and QSYM pair a fast coverage-guided fuzzer with on-demand concolic execution to punch through these walls.
13. AI-Augmented Fuzzing
The fuzzing landscape transformed dramatically with LLM integration in 2023-2026, shifting from manual harness construction and corpus curation to AI-driven automation across the entire fuzzing pipeline.
What AI brings (2026 capabilities)
| Task | Classical approach | 2026 AI approach |
|---|---|---|
| Harness generation | Manual, hours per target | LLM generates from API headers/documentation in minutes |
| Seed synthesis | Collect real samples | LLM generates grammar-valid seeds + format-compliant binaries |
| Mutation strategy selection | Hand-tuned schedules | RL agents learn optimal strategies per target dynamically |
| Crash triage | Manual stack analysis | LLM summarizes root cause + suggests fixes |
| Reachability guidance | Directed fuzzing + static analysis | LLM proposes inputs to reach deep targets |
| Dictionary creation | Manual keyword extraction | Auto-generated from codebase analysis |
| Coverage gap analysis | Manual LCOV inspection | AI identifies under-tested code paths |
| Input format learning | Manual grammar writing | LLM infers format from examples |
Production AI-fuzzing systems (2026)
OSS-Fuzz-Gen (Google Evolution):
- Enhanced pipeline — full repository analysis with dependency graph traversal, API surface detection, and build system auto-discovery
- Multi-language support — expanded from C/C++ to Java, Python, Rust, Go, and JavaScript targets
- Continuous harness optimization — LLM iteratively improves harnesses based on coverage feedback
- Integration with Jazzer — auto-generates JUnit
@FuzzTestmethods for Java projects - Results: 40+ newly-fuzzed projects, 50% average coverage increase, 15+ new vulnerabilities in production codebases
G2Fuzz (USENIX Security 2025):
- Generator-level mutation — LLMs write Python scripts that produce format-compliant inputs (TIFF, MP4, PDF, protobuf)
- Holistic + local search — mutates both the generator scripts and their outputs
- Multi-format support — handles structured binary formats that pure mutation struggles with
- Performance — outperformed AFL++, Fuzztruction, and FormatFuzzer on UNIFUZZ/MAGMA benchmarks
- Impact — 10 unique bugs, 3 CVE-confirmed in real-world software
MALF (Multi-Agent LLM Fuzzing):
- Domain-specific RAG — retrieval-augmented generation for ICS protocol knowledge
- QLoRA fine-tuning — specialized models for Modbus/TCP, S7Comm, Ethernet/IP
- Agent specialization — separate LLM agents for seed generation, mutation, and feedback analysis
- Real-world deployment — power plant attack-defense range with 3 zero-days found
- Success rate — 88-92% valid test case generation across industrial protocols
Advanced AI fuzzing techniques (2026)
Neurosymbolic fuzzing:
## Example: AI-guided constraint solving
def ai_solve_constraint(target_condition, current_input):
"""Use LLM to propose input modifications to satisfy branch conditions"""
prompt = f"""
Target wants to reach: {target_condition}
Current input: {current_input.hex()}
Current execution trace: {get_trace(current_input)}
Suggest 5 minimal byte modifications to satisfy the target condition.
Focus on magic numbers, length fields, checksums.
"""
suggestions = llm.generate(prompt)
return [apply_modification(current_input, mod) for mod in suggestions]
LLM-powered dictionary generation:
def generate_fuzzing_dictionary(codebase_path):
"""Auto-extract fuzzing keywords from source code"""
## Static analysis to find string literals, magic numbers, enum values
analysis = static_analyze(codebase_path)
prompt = f"""
Analyze this codebase for fuzzing dictionary entries:
String literals: {analysis.string_literals}
Magic numbers: {analysis.magic_numbers}
Protocol fields: {analysis.protocol_fields}
Error messages: {analysis.error_patterns}
Generate a libFuzzer dictionary with 100 high-value entries.
Focus on:
1. Protocol magic bytes and identifiers
2. Boundary values and special numbers
3. Common keywords and operators
4. Error-triggering sequences
"""
return llm.generate_dictionary(prompt)
Coverage-guided harness refinement:
def improve_harness_coverage(harness_code, coverage_report):
"""Iteratively improve fuzzing harness based on coverage gaps"""
prompt = f"""
Current fuzzing harness:
{harness_code}
Coverage report shows these uncovered paths:
{coverage_report.uncovered_functions}
Missing edge coverage in:
{coverage_report.cold_paths}
Suggest harness improvements to:
1. Reach uncovered functions
2. Exercise cold code paths
3. Add input validation bypasses
4. Test error handling branches
Provide modified harness code.
"""
return llm.refine_harness(prompt)
Language-specific AI fuzzing
Modern language support (2026):
Kotlin fuzzing with AI assistance:
// AI-generated coroutine fuzzing
@FuzzTest
suspend fun testCoroutineChannels(data: FuzzedDataProvider) {
val channelCount = data.consumeInt(1, 10)
val channels = (1..channelCount).map { Channel<String>(it) }
// AI suggests realistic async patterns
val producer = launch {
repeat(data.consumeInt(1, 100)) {
val msg = data.consumeString(50)
channels.random().send(msg)
}
}
// Test channel processing doesn't deadlock
withTimeout(1000) {
producer.join()
}
}
Rust fuzzing with AI-generated harnesses:
// AI infers complex API usage patterns
#[fuzz_target]
fn fuzz_async_runtime(data: &[u8]) -> Result<(), Box<dyn Error>> {
let mut cursor = Cursor::new(data);
// AI-suggested realistic usage pattern
let rt = Runtime::new()?;
rt.block_on(async {
let task_count = cursor.read_u8()? % 10 + 1;
let tasks: Vec<_> = (0..task_count)
.map(|_| {
let delay = cursor.read_u16()? % 1000;
tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(delay as u64)).await;
})
})
.collect();
// Test that task coordination doesn't panic
for task in tasks {
task.await?;
}
Ok(())
})
}
AI-powered crash analysis (2026)
Automated vulnerability assessment:
def ai_crash_analysis(crash_input, stack_trace, source_code):
"""AI-powered crash triage and exploitability analysis"""
prompt = f"""
CRASH ANALYSIS REQUEST
Stack trace:
{stack_trace}
Crashing input (hex): {crash_input.hex()}
Relevant source code:
{source_code}
Analyze this crash for:
1. Root cause (buffer overflow, null deref, logic error, etc.)
2. Exploitability potential (RCE, info leak, DoS only)
3. Recommended fix approach
4. Similar vulnerability patterns to check
5. CVSS score estimate
Format as structured JSON with confidence levels.
"""
analysis = llm.analyze_crash(prompt)
return {
'root_cause': analysis.root_cause,
'exploitability': analysis.exploitability_score,
'fix_suggestion': analysis.recommended_fix,
'similar_patterns': analysis.related_vulns,
'cvss_estimate': analysis.cvss_score
}
Practical AI-fuzzing workflow (2026)
Enterprise deployment pipeline:
- Repository ingestion — AI analyzes entire codebase, dependency graphs, build systems
- Attack surface mapping — LLM identifies all untrusted input entry points, API boundaries
- Harness auto-generation — creates fuzzing harnesses for top 20 attack surfaces
- Dictionary synthesis — extracts protocol keywords, magic bytes, boundary values
- Continuous fuzzing — runs 24/7 with AI-guided mutation strategy adaptation
- Intelligent crash triage — auto-classifies crashes, suggests fixes, estimates severity
- Coverage optimization — iteratively improves harnesses based on coverage gaps
- Regression prevention — auto-generates test cases from crashes to prevent reintroduction
Performance metrics (2026 benchmarks):
- Harness generation time: 5 minutes vs 2-4 hours manually
- Coverage improvement: 40-60% vs baseline manual fuzzing
- Bug discovery rate: 3x more unique vulnerabilities per CPU-hour
- False positive rate: 15% vs 40% for traditional crash clustering
- Triage time: 90% reduction from hours to minutes per crash
Limitations and challenges
AI fuzzing caveats (2026):
- Hallucination risk — LLMs may generate plausible but incorrect harnesses or analysis
- Context window limits — large codebases require chunking and may miss dependencies
- Cost considerations — continuous LLM usage for fuzzing can be expensive at scale
- Bias toward common patterns — may miss domain-specific or novel vulnerability classes
- Security of AI tools — LLMs trained on public code may leak patterns or inject vulnerabilities
Best practices:
- Validate AI-generated harnesses manually before production deployment
- Use multiple LLMs and cross-validate results for critical analysis
- Implement human review checkpoints for high-severity findings
- Monitor AI fuzzing costs and optimize prompt efficiency
- Maintain traditional fuzzing as a baseline comparison
14. JVM Fuzzing (Jazzer, LibAFL)
Java and JVM-language fuzzing has matured significantly with Jazzer (Code Intelligence) leading as the primary coverage-guided, in-process fuzzer that bridges the JVM and libFuzzer via JNI. Jazzer excels at finding logic bugs, injection vulnerabilities, and denial-of-service conditions in Java applications and libraries.
Jazzer architecture and core features
Coverage-guided engine:
- Jazzer Driver — a native binary linking libFuzzer. Calls
LLVMFuzzerRunDriverto start the C++ fuzzing loop. - Jazzer Agent — a Java agent that instruments JVM bytecode at runtime using JaCoCo and ASM. Coverage hooks call
CoverageMap.recordCoverage(int id)usingsun.misc.Unsafe.putByteto write directly into the shared coverage bitmap that libFuzzer reads via__sanitizer_cov_pcs_init. - The harness implements
fuzzerTestOneInput(byte[] input)or usesFuzzedDataProviderfor structured input consumption.
Built-in sanitizers (2026 enhancement):
- SSRF detection — automatically flags when user-controlled input reaches URL construction or HTTP client calls
- Path traversal detection — catches
../sequences in file operations - OS command injection detection — monitors when fuzz input flows into
Runtime.exec()orProcessBuilder - SQL injection detection — tracks fuzz data flowing into database query construction
- LDAP injection detection — flags untrusted input in LDAP searches
JUnit 5 integration (@FuzzTest annotation)
Modern testing workflow (2026):
import com.code_intelligence.jazzer.junit.FuzzTest;
import com.code_intelligence.jazzer.api.FuzzedDataProvider;
class MyFuzzTests {
@FuzzTest
void testJsonParser(FuzzedDataProvider data) {
String jsonInput = data.consumeRemainingAsString();
try {
JsonParser.parse(jsonInput);
} catch (JsonParseException expected) {
// Expected exception, not a bug
}
}
@FuzzTest(maxDuration = "5m")
void testUrlParsing(byte[] input) {
String url = new String(input);
try {
new URL(url);
} catch (MalformedURLException expected) {
// Expected exception
}
}
}
Regression testing mode:
@FuzzTest
@ValueSource(strings = {
"corpus/crash-001.json",
"corpus/crash-002.json"
})
void regressionTest(String inputFile) throws IOException {
byte[] input = Files.readAllBytes(Paths.get(inputFile));
// Test that previously crashing inputs no longer crash
JsonParser.parse(new String(input));
}
Advanced Jazzer techniques
Structured input consumption:
@FuzzTest
void testComplexAPI(FuzzedDataProvider data) {
// Consume structured data from fuzz input
int mode = data.consumeInt(0, 3);
String username = data.consumeString(20);
List<String> permissions = data.consumeList(
provider -> provider.consumeString(10), 5);
byte[] payload = data.consumeRemainingAsBytes();
UserService service = new UserService();
service.processRequest(mode, username, permissions, payload);
}
Custom sanitizers with method hooks:
import com.code_intelligence.jazzer.api.MethodHook;
import com.code_intelligence.jazzer.api.HookType;
public class CustomHooks {
@MethodHook(type = HookType.BEFORE, targetClassName = "java.lang.Runtime",
targetMethod = "exec", targetMethodDescriptor = "(Ljava/lang/String;)Ljava/lang/Process;")
public static void execHook(MethodHookInfo hookInfo, String command) {
// Flag potential command injection
if (command.contains("$(") || command.contains("`")) {
Jazzer.reportFindingFromHook(
new FuzzerSecurityIssueMedium("Command injection detected: " + command));
}
}
}
Kotlin-specific fuzzing patterns
kotlinx.fuzz integration (2026):
import kotlinx.fuzz.FuzzTest
import kotlinx.fuzz.FuzzedDataProvider
class KotlinFuzzTests {
@FuzzTest
fun testCoroutineFlow(data: FuzzedDataProvider) = runTest {
val eventCount = data.consumeInt(1, 1000)
val events = (1..eventCount).map {
data.consumeString(100)
}
val flow = events.asFlow()
.map { it.uppercase() }
.filter { it.isNotBlank() }
// Test that flow processing doesn't crash
flow.toList()
}
}
Practical Jazzer deployment
Production fuzzing setup:
## Download Jazzer
wget https://github.com/CodeIntelligenceTesting/jazzer/releases/latest/download/jazzer-linux.tar.gz
## Run with classpath and instrumentation
./jazzer \
--cp=target/classes:target/dependency/* \
--target_class=com.example.FuzzTarget \
--instrumentation_includes=com.example.** \
--instrumentation_excludes=com.example.test.** \
--max_len=65536 \
--dict=java-keywords.dict \
corpus/
CI/CD integration:
## GitHub Actions fuzzing job
- name: Run Jazzer fuzzing
run: |
## Build application
mvn compile dependency:copy-dependencies
## Run short fuzzing campaign (5 minutes)
timeout 300 ./jazzer \
--cp=target/classes:target/dependency/* \
--target_class=com.example.FuzzTarget \
--max_len=4096 \
corpus/ || true
## Check for new crashes
if [ -n "$(find . -name 'crash-*' -newer .last_build)" ]; then
echo "New crashes found!"
exit 1
fi
Advanced JVM fuzzing (2026)
AI-assisted harness generation:
- LLM-generated harnesses — point GPT/Claude at your Java API surface; generate
@FuzzTestmethods automatically - Dependency graph analysis — use static analysis to identify untrusted input entry points and auto-generate fuzzing targets
- Grammar-aware input synthesis — LLMs generate valid JSON/XML/protocol buffers as structured fuzz inputs
Multi-language JVM fuzzing:
// Scala fuzzing
@FuzzTest
def testScalaParser(data: FuzzedDataProvider): Unit = {
val input = data.consumeRemainingAsString()
Try(ScalaParser.parse(input)) match {
case Success(_) => // OK
case Failure(_: ParseException) => // Expected
case Failure(other) => throw other // Unexpected crash
}
}
// Clojure fuzzing via Java interop
@FuzzTest
public void testClojureEval(FuzzedDataProvider data) {
String code = data.consumeRemainingAsString();
try {
Clojure.`eval`(Clojure.read(code));
} catch (Exception expected) {
// Most random strings will fail to parse/eval
}
}
Jazzer + LibAFL evolution
LibFuzzer is in maintenance mode, limiting Jazzer’s evolution. The Team Atlanta AIxCC project created a fork replacing libFuzzer with LibAFL as the fuzzing backend, bringing:
- Superior mutation strategies — LibAFL’s composable mutators vs libFuzzer’s fixed strategy
- Better scheduling — multiple scheduling algorithms vs libFuzzer’s simple queue
- Distributed fuzzing — multi-node coordination built-in
- Custom feedback — beyond edge coverage to value coverage, taint tracking, custom metrics
LibAFL-Jazzer performance benefits:
- Higher throughput — LibAFL’s rust-optimized execution pipeline
- Better corpus management — intelligent seed selection and minimization
- Advanced instrumentation — context-sensitive coverage, value profiling, control-flow integrity checks
JVM fuzzing targets that matter
High-impact vulnerability classes:
- Deserialization bugs —
ObjectInputStreamwith attacker-controlled data - XML external entity (XXE) — XML parsers with external entity processing
- Expression language injection — SpEL, OGNL, JSP EL with user input
- Template injection — FreeMarker, Velocity, Thymeleaf templates
- SQL injection — ORM query building with dynamic input
- JNDI injection — LDAP/RMI lookups with user-controlled names
- Path traversal — file operations with relative paths
- Server-side request forgery (SSRF) — HTTP clients with user-controlled URLs
Memory-safe language caveats:
While Java’s memory safety eliminates buffer overflows and use-after-free bugs, focus on:
- Logic bugs and state corruption
- Infinite loops and resource exhaustion
- Injection vulnerabilities detectable via custom sanitizers
- Business logic violations (authentication bypass, privilege escalation)
- Exception handling edge cases
15. Rust & Python Fuzzing
Rust fuzzing (2026 advances)
Rust’s memory safety guarantees reduce but do not eliminate fuzzing value — unsafe blocks, logic bugs, panics, integer overflows, and complex async behavior remain valid targets.
| Tool | Backend | Notes |
|---|---|---|
| cargo-fuzz | libFuzzer | The standard Rust fuzzer; wraps libfuzzer-sys |
| cargo-libafl | LibAFL | Drop-in replacement for cargo-fuzz using LibAFL’s engine. Superior performance and extensibility |
| afl.rs | AFL++ | AFL++ wrapper for Rust targets |
| cargo-bolero | Multiple | Unified testing framework supporting libFuzzer, honggfuzz, and AFL++ |
## cargo-fuzz quickstart
cargo install cargo-fuzz
cargo fuzz init
cargo fuzz add my_target
cargo fuzz run my_target -- -max_len=4096
## cargo-libafl with advanced features (2026)
cargo install cargo-libafl
cargo libafl init
cargo libafl fuzz my_target --jobs 8 --coverage context-sensitive
Modern Rust fuzzing patterns (2026):
Async and concurrency fuzzing:
#![no_main]
use libfuzzer_sys::fuzz_target;
use tokio::runtime::Runtime;
use std::time::Duration;
fuzz_target!(|data: &[u8]| {
if data.len() < 8 { return; }
let rt = Runtime::new().unwrap();
rt.block_on(async {
// Fuzz async state machines and race conditions
let task_count = data[0] % 10 + 1;
let mut tasks = Vec::new();
for i in 0..task_count {
let delay = u16::from_be_bytes([data[i*2+1], data[i*2+2]]) % 1000;
tasks.push(tokio::spawn(async move {
tokio::time::sleep(Duration::from_millis(delay as u64)).await;
// Process some data that might panic
process_async_data(&data[8..]).await;
}));
}
// Test that joining tasks doesn't hang or panic
for task in tasks {
let _ = tokio::time::timeout(Duration::from_millis(5000), task).await;
}
});
});
Property-based fuzzing with arbitrary:
use arbitrary::{Arbitrary, Unstructured};
#[derive(Debug, Arbitrary)]
struct FuzzConfig {
buffer_size: u16,
compression_level: u8,
enable_checksums: bool,
timeout_ms: u32,
}
fuzz_target!(|data: &[u8]| {
let mut unstructured = Unstructured::new(data);
// Generate structured configuration
let config: FuzzConfig = match unstructured.arbitrary() {
Ok(c) => c,
Err(_) => return, // Not enough data
};
// Use remaining bytes as payload
let payload = unstructured.take_rest();
// Test the system with structured config + arbitrary payload
let mut processor = DataProcessor::new(config.buffer_size);
processor.set_compression(config.compression_level);
processor.enable_checksums(config.enable_checksums);
processor.set_timeout(Duration::from_millis(config.timeout_ms as u64));
let _ = processor.process(payload);
});
cargo-libafl provides significant advantages over cargo-fuzz:
- LibAFL’s advanced mutators — grammar-aware, value-profile-guided mutations
- Custom feedback types — beyond edge coverage to value coverage, taint tracking
- Better scheduling — multiple algorithms vs libFuzzer’s simple queue
- Distributed fuzzing — multi-machine coordination built-in
Python fuzzing with Atheris
Atheris is Google’s coverage-guided Python fuzzer, pip-installable and simple to set up. It instruments Python bytecode for coverage feedback and can also instrument native C extensions via libFuzzer.
import atheris
import sys
def test_one_input(data):
fdp = atheris.FuzzedDataProvider(data)
s = fdp.ConsumeUnicode(100)
try:
my_module.parse(s)
except (ValueError, TypeError):
pass # expected exceptions
atheris.Setup(sys.argv, test_one_input)
atheris.Fuzz()
Key Atheris patterns:
- Use
atheris.FuzzedDataProviderto consume typed data (strings, ints, floats) from the raw byte buffer. - Catch expected exceptions explicitly; let unexpected ones propagate as findings.
- For C extensions, compile with
-fsanitize=fuzzer-no-link,addressand Atheris will hook into libFuzzer’s coverage. - Python fuzzing finds logic bugs, unhandled edge cases, and crash-inducing inputs in parsing code — not memory corruption (unless C extensions are involved).
16. Snapshot Fuzzing (Nyx, HyperHook)
Snapshot fuzzing captures a program’s state (memory, registers, execution context) at a specific point, then restores it after each test case. This eliminates startup overhead and enables fuzzing of deeply-nested, stateful, or long-running targets.
Nyx
Nyx is a hypervisor-based snapshot fuzzer using a modified QEMU-Nyx and KVM-Nyx. It:
- Creates and restores VM snapshots at near-native speed via KVM.
- Uses Intel Processor Trace (PT) for low-overhead coverage collection.
- Communicates between guest and host via custom hypercalls.
- Supports both Linux and Windows guest targets.
HyperHook
HyperHook (Neodyme) is a harnessing framework that simplifies building Nyx agents. It abstracts:
- Guest-to-host communication (hypercall wrappers).
- Target function harnessing (setting entry points, placing fuzz input).
- Exception handling setup.
- Cross-platform support (Linux and Windows user-space targets).
A typical HyperHook + Nyx + LibAFL setup:
- LibAFL handles input generation, mutation, and scheduling on the host.
- Nyx manages VM snapshots, coverage via Intel PT, and guest execution.
- HyperHook runs inside the guest, harnessing the target function and signaling the host.
Snapshot fuzzing is particularly effective for:
- Applications with long startup phases (databases, browsers).
- Multithreaded targets where fork-mode fuzzing is unreliable.
- Closed-source binaries where recompilation is not possible.
- Kernel-mode targets (full-system snapshot with KVM).
17. Smart Contract Fuzzing
Blockchain smart contracts are high-value fuzzing targets — bugs directly translate to financial loss.
Medusa
Medusa (Trail of Bits) is an open-source EVM-based smart contract fuzzer built on Geth, successor to Echidna. Key features:
- Coverage-guided fuzzing with HTML coverage reports.
- Parallel fuzzing — scales across CPU cores for faster campaigns.
- Smart mutational value generation — leverages runtime values and Slither static analysis to optimize inputs.
- On-chain fuzzing — seeds state with values fetched from live blockchain data.
- Property-based testing — write Solidity invariants that Medusa tries to violate.
brew install medusa # macOS
medusa init # generates medusa.json config
medusa fuzz # start fuzzing
Echidna
The predecessor to Medusa, written in Haskell. Still maintained for bug fixes but development focus has shifted to Medusa. Echidna pioneered property-based smart contract fuzzing with Solidity assertion checking.
Smart contract fuzzing targets
- Invariant violations — token balances don’t add up, access control bypassed.
- Reentrancy — external calls that re-enter the contract before state updates.
- Integer overflow/underflow — in pre-Solidity-0.8 contracts without SafeMath.
- Flash loan attacks — price manipulation via large temporary borrows.
- Governance attacks — voting manipulation via proposal parameter fuzzing.
18. Protocol & Network Fuzzing (Boofuzz, ICS)
Boofuzz
Boofuzz is the modern successor to the Sulley framework — a Python-based, modular protocol fuzzer for stateful network services. Unlike web fuzzers that target HTTP, Boofuzz targets arbitrary TCP/UDP protocols.
from boofuzz import *
session = Session(
target=Target(connection=SocketConnection("192.168.1.1", 502, proto='tcp'))
)
s_initialize("modbus_read")
s_word(0x0001, name="transaction_id", fuzzable=True)
s_word(0x0000, name="protocol_id")
s_word(0x0006, name="length")
s_byte(0x01, name="unit_id")
s_byte(0x03, name="function_code")
s_word(0x0000, name="start_address", fuzzable=True)
s_word(0x000A, name="quantity", fuzzable=True)
session.connect(s_get("modbus_read"))
session.fuzz()
Boofuzz features:
- Stateful protocol modeling — define multi-step protocol sequences (e.g., login then command).
- Process monitoring — detect target crashes via process monitors, serial port monitors, or custom callbacks.
- Web UI — real-time fuzzing progress dashboard.
- Extensible primitives —
s_string,s_byte,s_word,s_dword,s_group,s_blockfor structured protocol fields.
ICS protocol fuzzing
Industrial control protocols (Modbus/TCP, S7Comm, Ethernet/IP, DNP3, IEC 61850, OPC UA) present unique challenges:
- Stateful, sequence-dependent — commands must follow specific handshake sequences.
- Timing-sensitive — real-time constraints affect crash detection.
- Limited feedback — many PLCs have no crash reporting; monitoring requires external observation (power draw, LED states, network responses).
- Safety-critical — fuzzing live ICS equipment risks physical damage; use isolated testbeds.
Tools: Boofuzz (protocol modeling), MALF (LLM-guided), ISF (Industrial Security Framework), custom syzkaller descriptions for ICS-relevant kernel interfaces.
19. Crash Triage & Minimization
A good fuzzing run produces hundreds or thousands of crashes — most are duplicates of a handful of real bugs.
Deduplication
Group crashes by:
- Top-N stack frame hash (typical: top 3 frames, ignoring libc/sanitizer frames)
- Bug type (UAF vs stack OOB vs integer overflow)
- Crashing instruction address (rough, changes with PIE/ASLR)
Tools: casr, exploitable GDB plugin, AFL’s afl-collect, libFuzzer’s -dedup_token=.
Minimization
Reduce crashing inputs to their smallest reproducing form so you can actually read them.
## AFL++
afl-tmin -i crash_input -o crash_min -- ./target @@
## libFuzzer
./fuzz -minimize_crash=1 -runs=10000 crash_input
Minimized inputs make root cause analysis dramatically easier and produce smaller, more publishable PoCs.
Crash analysis
## GDB with the crashing input
gdb ./target -ex "run < crash_min" -ex "bt full" -ex "quit"
## rr for time-travel debugging
rr record ./target crash_min
rr replay
## then (rr) reverse-next, reverse-continue, etc.
ASan reports already give you:
- Bug type and summary line
- Allocation, free, and use stacks (for UAF)
- Shadow memory dump around the faulting address
For exploitability assessment, combine the ASan report with register state and the CFG of the crashing function.
Severity triage rough rubric
| Signal | Likely severity |
|---|---|
| Heap OOB write, UAF, double-free | High — often exploitable |
| Stack buffer overflow | High — often exploitable without stack cookies |
| Heap OOB read | Medium — info leak |
| Uninitialized memory read | Medium — info leak |
| NULL deref | Low — DoS only, usually |
| Integer overflow without memory effect | Low — unless it sizes an allocation |
| Assertion failure / hang | Low — DoS |
20. CI/CD Integration
Fuzzing pays off when it runs continuously. A one-shot fuzz campaign finds the easy bugs; continuous fuzzing catches regressions.
Short-run CI fuzzing
On every PR, run each fuzz target for 60-300 seconds against the current corpus. Fail the build on new crashes. This catches obvious regressions without slowing merges.
## .github/workflows/fuzz.yml (sketch)
name: Fuzz
on: [pull_request]
jobs:
fuzz:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y clang llvm
- run: clang++ -fsanitize=fuzzer,address fuzz_target.cc target.cc -o fuzz
- uses: actions/cache@v4
with:
path: corpus/
key: fuzz-corpus-${​{ github.ref }}
- run: ./fuzz corpus/ -max_total_time=120 -max_len=4096
Continuous fuzzing platforms
| Platform | Owner | Strengths |
|---|---|---|
| OSS-Fuzz | Free for open-source; runs thousands of projects | |
| ClusterFuzz | Self-hostable infrastructure behind OSS-Fuzz | |
| ClusterFuzzLite | Lightweight CI-focused variant | |
| OneFuzz | Microsoft | Windows-first, cloud-native; archived but usable |
| Mayhem | ForAllSecure | Commercial, strong binary-only support |
| Code Intelligence CI Fuzz | Commercial | Java/Kotlin-friendly, enterprise |
OSS-Fuzz integration requires writing a Dockerfile, build.sh, and one or more fuzz targets per project. Google then runs the targets continuously across ASan, MSan, and UBSan builds, files bugs automatically, and enforces a 90-day disclosure window.
CI fuzzing best practices
- Cache the corpus between runs — otherwise each CI run starts from scratch.
- Time-box runs with
-max_total_timeso CI doesn’t stall. - Upload crashes as artifacts for offline triage.
- Run longer campaigns nightly/weekly alongside the short PR runs.
- Gate merges on zero new crashes, not on coverage deltas (which are noisy).
- Store a golden corpus in object storage and pull it fresh per run.
21. Bugs That Survive Continuous Fuzzing
Even years of continuous OSS-Fuzz enrollment does not guarantee security. A GitHub Security Lab study documented three high-profile examples and a systematic five-step workflow to address the gaps.
Why bugs survive
Insufficient harness coverage (GStreamer): Enrolled in OSS-Fuzz for 7 years with only 2 active fuzzers and 19% code coverage. By comparison, OpenSSL has 139 fuzzers. In December 2024, 29 new vulnerabilities were found manually — including high-risk issues — because nobody had written harnesses for under-covered parsers.
Unfuzzed dependencies (Poppler/DjVuLibre): Poppler’s OSS-Fuzz integration covered Poppler itself at ~60% but did not instrument external dependencies like DjVuLibre. A critical 1-click RCE (CVE-2025-53367) was found in DjVuLibre — a dependency shipped by default with Evince/Papers on millions of Ubuntu systems but never fuzzed at all.
Neglected attack surfaces (Exiv2): Despite 3+ years of OSS-Fuzz enrollment and multiple CVEs found (CVE-2024-39695, CVE-2024-24826, CVE-2023-44398), new vulnerabilities (CVE-2025-26623, CVE-2025-54080) were reported by external researchers because encoding functions received far less fuzzing attention than decoding functions.
The five-step fuzzing workflow
- Code preparation — remove checksums, reduce randomness, drop unnecessary delays, fix signal handling to make the target fuzzer-friendly.
- Improving code coverage — iterative cycle of running fuzzers, checking LCOV reports for uncovered areas, writing new harnesses or input cases. Target >90% edge coverage before moving on. Fuzz both obvious surfaces (decoders, parsers) and non-obvious ones (encoders, muxers, file writers).
- Context-sensitive coverage — switch from flat edge coverage to context-sensitive or N-gram coverage to distinguish execution paths that share the same edges but differ in calling context. Target >60%.
- Value coverage — instrument key variables with range-splitting branches so the fuzzer explores different value domains, catching arithmetic and boundary bugs missed by edge coverage alone.
- Triaging — systematic crash analysis and deduplication.
Advanced techniques for step 2: fault injection (simulating malloc failures, partial I/O, missing files) and snapshot fuzzing (AFL++ QEMU/Nyx modes) for stateful targets.
22. Real-World Wins & CVEs
Fuzzing’s track record is staggering. A partial sampling:
Browser engines
- V8 / Chrome — Fuzzilli has found dozens of JIT bugs, many exploited in the wild by state actors before discovery.
- JavaScriptCore — similar story, frequent fuzzing finds turn into in-the-wild iOS exploits.
- SpiderMonkey — LangFuzz, jsfunfuzz, and Fuzzilli produce a steady stream of bugs.
Multimedia & document parsers
- GStreamer — 29 new vulnerabilities discovered in December 2024 despite 7 years of OSS-Fuzz enrollment, because only 2 harnesses existed covering 19% of code. Demonstrates that enrollment without harness maintenance is insufficient.
- Poppler / DjVuLibre — CVE-2025-53367: a 1-click RCE in DjVuLibre (the DjVu parser shipped with Ubuntu’s Evince). The dependency was never included in Poppler’s OSS-Fuzz build despite being installed on millions of systems by default.
- Exiv2 — CVE-2024-39695, CVE-2024-24826, CVE-2023-44398 (found by OSS-Fuzz), plus CVE-2025-26623 and CVE-2025-54080 (found by external researchers) — encoding-side bugs that surviving continuous fuzzing focused on decoding.
System libraries
- libjpeg, libpng, libtiff, libxml2, libxslt, libyaml — hundreds of CVEs from AFL and libFuzzer campaigns; most are now OSS-Fuzz targets.
- OpenSSL / BoringSSL — Heartbleed was not found by fuzzing, but many subsequent parser/ASN.1 bugs were. Honggfuzz and OSS-Fuzz routinely surface new issues.
- ImageMagick — “the tarpit” — an enormous parade of CVEs, many fuzzing-found, many in obscure format parsers.
Language runtimes
- JerryScript —
CVE-2023-36109, an OOB read inecma_stringbuilder_append_rawreached via regex replace substitution, reproduced and localized using instrumented Fuzzilli with per-edge symbolization. - PHP — regular fuzzing finds in the ZIP, phar, and unserialize paths.
- Python — CPython fuzzing targets routinely turn up bugs in the C extensions.
Network stacks
- Linux kernel netlink / netfilter / TCP stack — Syzkaller bugs numbering in the hundreds.
- QUIC implementations — differential fuzzing across ngtcp2, quiche, msquic finds protocol-compliance bugs.
- DNS resolvers — dnsmasq, unbound, BIND fuzz finds with structured generators.
Kernel & firmware
- Linux kernel — Syzbot has filed thousands of bugs; KASAN + KCOV + KMSAN.
- Android / Fuchsia — Syzkaller ports find LPEs and driver bugs.
- Windows kernel — OneFuzz and commercial fuzzers find driver-level CVEs.
Industrial control / embedded
- ICS protocols (Modbus, DNP3, IEC 61850) — MALF-style LLM frameworks and classical protocol fuzzers have surfaced pre-authentication RCE in multiple PLC firmwares. MALF identified 3 zero-day flaws in a power plant attack-defense range deployment (one CNVD-registered).
- USB stack fuzzing (syzkaller’s
usb-fuzzer) — dozens of Linux USB driver UAFs.
Smart contracts
- Medusa / Echidna — Trail of Bits’ fuzzers have found invariant violations, reentrancy bugs, and access control bypasses in production DeFi protocols. Medusa’s on-chain seeding mode catches bugs that only manifest with real blockchain state.
- G2Fuzz — 10 unique bugs in latest real-world software using LLM-synthesized input generators, 3 CVE-confirmed.
Web apps
- Directory/endpoint fuzzing with ffuf/feroxbuster regularly surfaces
/.git/,/.env,/backup.zip,/api/internal, admin panels, and dev endpoints on real bounty targets. - Parameter brute-forcing finds hidden debug flags (
?debug=1,?admin=true). - GraphQL introspection + field fuzzing finds IDOR/BOLA at scale.
23. Tools & Frameworks Reference
Coverage-guided engines (2026 edition)
| Tool | Language | 2026 Strengths |
|---|---|---|
| AFL++ | C/C++/Rust via afl-clang-fast, Python via hooks | CMPLOG, LAF-INTEL, QEMU/Frida modes, context-sensitive coverage |
| libFuzzer | C/C++ | In-process, extremely fast, LLVM-integrated (maintenance mode since 2024) |
| LibAFL | C/C++/Rust/Java/Python | Modular architecture, custom feedback, distributed fuzzing, active development |
| Centipede | C/C++ | Google’s distributed successor to libFuzzer, production-ready 2026 |
| honggfuzz | C/C++ | Hardware-assisted coverage (Intel PT/BTS), persistent mode |
| Jazzer | Java/Kotlin/Scala | JUnit 5 integration, built-in sanitizers (SSRF, SQLi, command injection) |
| cargo-fuzz | Rust | libFuzzer wrapper for Rust crates, async/concurrency support |
| cargo-libafl | Rust | LibAFL-based, superior performance, value coverage, taint tracking |
| cargo-bolero | Rust | Unified framework supporting multiple fuzzing backends |
| go-fuzz / Go native fuzzing | Go | Built into go test since Go 1.18, goroutine race detection |
| Atheris | Python | Coverage-guided Python fuzzing, C extension support |
| kotlinx.fuzz | Kotlin | Native Kotlin fuzzing with coroutine and multiplatform support |
| jsfuzz | JavaScript | Node.js coverage-guided fuzzer, V8 integration |
Grammar / structure-aware
| Tool | Target |
|---|---|
| Fuzzilli | JavaScript engines (V8, JSC, SpiderMonkey, JerryScript) |
| Domato | HTML/CSS/JS DOM rendering |
| Dharma / Grammarinator | Arbitrary grammars |
| libprotobuf-mutator | Protobuf-shaped inputs |
| Peach / SPIKE | Network protocols |
| Radamsa | Black-box mutation of structured text |
| G2Fuzz | LLM-synthesized generators for non-textual inputs (TIFF, MP4, PDF) |
Web & API
| Tool | Purpose |
|---|---|
| ffuf | Fast HTTP fuzzer: directories, parameters, subdomains, bodies |
| feroxbuster | Recursive content discovery |
| wfuzz | Multi-point HTTP fuzzer with rich filters |
| Gobuster | Simple, fast directory/subdomain brute-forcing |
| Burp Suite Intruder | Payload-driven parameter fuzzing |
| Turbo Intruder | Burp extension for high-speed, race-condition fuzzing |
| Schemathesis | OpenAPI property-based fuzzer |
| RESTler | Stateful REST fuzzer |
| EvoMaster | Search-based REST/GraphQL fuzzer with JUnit output |
| InQL | GraphQL schema extractor + fuzzer |
| Arjun / ParamMiner | HTTP parameter discovery |
Protocol & ICS
| Tool | Purpose |
|---|---|
| Boofuzz | Stateful network protocol fuzzer (successor to Sulley) |
| MALF | Multi-agent LLM framework for ICS protocol fuzzing |
| ISF | Industrial Security Framework for SCADA/ICS |
Smart contract
| Tool | Purpose |
|---|---|
| Medusa | EVM-based coverage-guided fuzzer (Trail of Bits, successor to Echidna) |
| Echidna | Property-based Haskell smart contract fuzzer |
Snapshot & harnessing
| Tool | Purpose |
|---|---|
| Nyx | Hypervisor-based snapshot fuzzing via KVM + Intel PT |
| HyperHook | Harnessing framework for Nyx (Neodyme) |
Kernel / OS
| Tool | Target |
|---|---|
| Syzkaller / syzbot | Linux / BSD / Fuchsia / Windows kernels |
| Trinity | Linux syscall fuzzer |
| kAFL / Nyx / HyperHook | Snapshot-based full-system fuzzing + harnessing framework |
| usb-fuzzer | Linux USB subsystem |
| KCOV | Kernel coverage collection API |
Continuous platforms
| Platform | Notes |
|---|---|
| OSS-Fuzz | Free for open source, run by Google |
| ClusterFuzz / ClusterFuzzLite | Self-hostable |
| OneFuzz | Microsoft’s platform |
| Mayhem | ForAllSecure commercial |
Mutation / test generation
| Tool | Notes |
|---|---|
| Radamsa | Language-agnostic text mutator |
| zzuf | Transparent stream mutator |
| FuzzedDataProvider | libFuzzer helper for structured harness inputs |
24. Wordlist & Corpus Resources
Web wordlists
| Source | Use |
|---|---|
SecLists (danielmiessler/SecLists) | The canonical collection: directories, parameters, subdomains, fuzzing payloads |
Assetnote wordlists (assetnote.io/resources/downloads) | Tech-specific: wordpress, laravel, tomcat, etc. |
| raft-medium/large-directories.txt | Standard directory discovery lists |
| raft-large-words.txt | General word dictionary |
| api-endpoints.txt (SecLists) | REST endpoint guesses |
| graphql.txt | GraphQL operation names |
| subdomains-top1million-110000.txt | Subdomain brute-forcing |
| rockyou.txt | Credential stuffing / parameter value spraying |
| PayloadsAllTheThings | SQLi, XSS, SSRF, SSTI, command injection payloads |
Binary / format corpora
| Source | Use |
|---|---|
| OSS-Fuzz corpus backups | Public GCS bucket per target |
| Mozilla fuzzdata | Browser formats |
| Fuzzer Test Suite | Historical Google benchmark seeds |
| CERT BFF samples | General format seeds |
| Synthetic PDF/PNG/JPEG suites | Stress-test files |
Dictionaries
| Source | Use |
|---|---|
AFL++ dictionaries/ | Ships with AFL++; covers XML, SQL, PDF, HTML, JS, and more |
| libFuzzer examples | compiler-rt/lib/fuzzer/dictionaries/ |
| Awesome-Fuzzing | Curated links to everything above |
25. Quick Reference Cheatsheet
Build a libFuzzer target
clang++ -g -O1 -fsanitize=fuzzer,address,undefined \
-fno-sanitize-recover=all -fno-omit-frame-pointer \
fuzz_target.cc target.cc -o fuzz
./fuzz corpus/ -max_len=4096 -dict=keywords.dict -jobs=8
Build and run AFL++
AFL_USE_ASAN=1 afl-clang-fast -o target target.c
afl-fuzz -i corpus -o findings -- ./target @@
afl-fuzz -i corpus -o findings -M master -- ./target @@ # main node
afl-fuzz -i corpus -o findings -S slave1 -- ./target @@ # secondary
Minimize a crash
afl-tmin -i crash -o crash_min -- ./target @@
./fuzz -minimize_crash=1 -runs=100000 crash
Merge/minimize a corpus
./fuzz -merge=1 corpus_min/ corpus/
afl-cmin -i corpus -o corpus_min -- ./target @@
ffuf one-liners
## Directories
ffuf -u https://t/FUZZ -w raft-medium.txt -t 50 -fc 404
## Subdomains
ffuf -u https://FUZZ.t.com -w subs.txt -H "Host: FUZZ.t.com"
## Parameters (GET)
ffuf -u "https://t/api?FUZZ=test" -w params.txt -fs 0
## JSON body
ffuf -u https://t/api -X POST -H "Content-Type: application/json" \
-d '{"id":"FUZZ"}' -w ids.txt -mc 200
## Virtual hosts
ffuf -u https://t -H "Host: FUZZ.t.com" -w subs.txt -fs 1234
libFuzzer flag cheatsheet
| Flag | Meaning |
|---|---|
-max_len=N | Cap input size |
-dict=f | Load dictionary |
-jobs=N | Run N child processes |
-workers=N | Parallel workers |
-runs=N | Stop after N iterations |
-timeout=N | Per-input timeout (seconds) |
-rss_limit_mb=N | Memory cap |
-fork=N | Fork mode for crash isolation |
-merge=1 dst src | Minimize corpus |
-minimize_crash=1 | Shrink a reproducer |
-seed=N | Deterministic seed |
-print_final_stats=1 | Dump coverage stats on exit |
Sanitizer flag combos
## Development default
-fsanitize=address,undefined -fno-sanitize-recover=all
## Heavy uninit detection (all deps must be MSan-built)
-fsanitize=memory -fsanitize-memory-track-origins=2
## Data races
-fsanitize=thread
## Integer overflow only
-fsanitize=signed-integer-overflow,unsigned-integer-overflow
Harness template (libFuzzer, C++)
#include <cstddef>
#include <cstdint>
#include <fuzzer/FuzzedDataProvider.h>
#include "target.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
if (size < 8) return 0;
FuzzedDataProvider fdp(data, size);
int mode = fdp.ConsumeIntegralInRange<int>(0, 3);
auto input = fdp.ConsumeRemainingBytes<uint8_t>();
Target t;
t.process(mode, input.data(), input.size());
return 0;
}
AFL++ environment knobs
| Variable | Effect |
|---|---|
AFL_USE_ASAN=1 | Build with AddressSanitizer |
AFL_USE_UBSAN=1 | Build with UBSan |
AFL_USE_MSAN=1 | Build with MemorySanitizer |
AFL_LLVM_CMPLOG=1 | Enable CMPLOG magic-value solving |
AFL_LLVM_LAF_ALL=1 | Enable all LAF-INTEL transforms |
AFL_SKIP_CPUFREQ=1 | Skip CPU frequency scaling check |
AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 | Skip core-pattern check |
AFL_PERSISTENT=1 | Hint persistent-mode harness |
AFL_TMPDIR=/dev/shm/afl | Put queue on tmpfs for speed |
Jazzer one-liner
## Run Jazzer on a Java target
jazzer --cp=target.jar --target_class=com.example.FuzzTarget \
--instrumentation_includes=com.example.** corpus/
Atheris one-liner
python3 -m atheris.instrument_all -- python3 my_fuzzer.py corpus/
Medusa one-liner
medusa init && medusa fuzz --workers 8
Boofuzz skeleton
from boofuzz import *
session = Session(target=Target(connection=SocketConnection("host", 80)))
s_initialize("request")
s_string("FUZZ", fuzzable=True)
session.connect(s_get("request"))
session.fuzz()
Modern fuzzing workflow (2026)
AI-assisted fuzzing pipeline:
## 1. Repository analysis and harness generation
claude-code analyze --repo . --output-harnesses fuzz/
jazzer-gen --classpath target/lib/ --package com.example --output fuzz/java/
oss-fuzz-gen --project . --languages c,cpp,java,python
## 2. Advanced instrumentation build
clang++ -g -O1 \
-fsanitize=fuzzer,address,undefined,bounds \
-fsanitize-coverage=trace-pc-guard,trace-cmp,trace-div \
-fno-sanitize-recover=all target.cc -o fuzz_target
## 3. AI-generated dictionary and corpus
claude-code extract-dictionary --source . --output keywords.dict
claude-code synthesize-corpus --format json --count 100 --output corpus/
## 4. Multi-modal fuzzing campaign
./fuzz_target corpus/ -dict=keywords.dict -jobs=16 -workers=16 \
-max_len=65536 -timeout=30 -rss_limit_mb=8192
## 5. Intelligent crash triage
for crash in crash-*; do
claude-code analyze-crash --input $crash --binary fuzz_target \
--source target.cc --output triage-$crash.json
done
## 6. Continuous improvement
claude-code optimize-harness --coverage-report coverage.lcov \
--harness fuzz_target.cc --output fuzz_target_v2.cc
Crash triage checklist (2026 enhanced)
- Automated deduplication — Is this a duplicate? (AI-powered stack similarity + root cause analysis)
- Sanitizer classification — What does ASan/UBSan/custom sanitizers report?
- Input minimization — Does AI-guided minimization produce readable PoC?
- Determinism check — Is it deterministic across reruns and different environments?
- Attack surface validation — Is the crashing input reachable from real, attacker-controlled input?
- Impact assessment — Can you demonstrate impact beyond DoS? (AI-suggested exploitation paths)
- Exploitability scoring — Use AI analysis to estimate CVSS score and exploitation difficulty
- Fix development — Apply patch, add regression test, verify fix with extended fuzzing
- Variant analysis — Use AI to suggest similar vulnerability patterns to investigate
Closing Notes
Fuzzing in 2026 is not a replacement for code review, unit tests, or threat modeling — it’s an AI-amplified force multiplier. A single good harness paired with modern sanitizers, AI-guided mutations, and distributed compute will surface vulnerabilities that manual audits miss, often in minutes rather than weeks. But the hard part has shifted from running the fuzzer to orchestrating the entire AI-assisted security pipeline.
The 2026 playbook:
- AI-powered surface analysis — Use LLMs to identify all untrusted-input surfaces, not just obvious parsers.
- Auto-generated harness portfolio — Deploy OSS-Fuzz-Gen, Jazzer, or custom AI tools to generate 20+ harnesses per project.
- Multi-modal instrumentation — Combine edge coverage, value coverage, taint tracking, and custom sanitizers.
- Intelligent corpus synthesis — AI generates format-compliant seeds plus real-world samples.
- Adaptive fuzzing campaigns — RL-guided mutation strategies that evolve based on target characteristics.
- Real-time crash intelligence — Automated triage, exploitability assessment, and fix suggestions.
- Continuous security regression — CI/CD integration with coverage-gap analysis and harness optimization.
- Language-specific optimization — Jazzer for JVM, cargo-libafl for Rust, kotlinx.fuzz for multiplatform.
- Enterprise integration — Snapshot fuzzing (Nyx), distributed campaigns (LibAFL), and compliance reporting.
- Variant hunting — AI-suggested code patterns and attack surfaces based on discovered vulnerabilities.
Key 2026 innovations that changed the game:
- Jazzer’s built-in sanitizers detect injection vulnerabilities (SSRF, SQLi, command injection) during fuzzing
- AI harness generation reduces setup time from hours to minutes with better coverage than manual work
- Value and taint tracking catches arithmetic bugs and magic-value dependencies that edge coverage misses
- Modern language support brings coverage-guided fuzzing to Kotlin coroutines, Rust async, and modern JVM frameworks
- Neurosymbolic fuzzing combines LLM constraint solving with traditional mutation for deeper path exploration
The cost equation has flipped:
- 2020: High harness engineering cost, low compute cost, manual triage bottleneck
- 2026: AI-automated harness generation, higher compute cost, automated vulnerability intelligence
The bugs are still there. The tooling is now AI-enhanced. The main cost is compute and AI inference, not engineering time. Organizations that don’t adopt AI-augmented fuzzing will miss entire vulnerability classes that their AI-equipped competitors catch routinely.
ROI reality check: A single RCE caught by AI-assisted fuzzing before production release pays for months of fuzzing infrastructure. A single data breach prevented pays for years of continuous fuzzing across your entire codebase.
The future is continuous, intelligent, AI-guided security testing. The question isn’t whether to adopt these techniques — it’s how quickly you can deploy them before your adversaries find the bugs first.