AFL++ and Honggfuzz both sit at the top tier of coverage-guided fuzzers for C and C++ targets. They have found thousands of CVEs between them across the Linux kernel, browsers, media decoders, TLS libraries, and virtually every other class of attack surface. The question of which to use is not a matter of one being strictly better: they optimize for different things, and the best fuzzing programs run both.
This post assumes you have already read the libFuzzer vs AFL++ comparison and are choosing between the two non-libFuzzer options for a specific target.
Shared Lineage, Divergent Designs
Both tools trace back to the same 2013–2014 era of coverage-guided fuzzing that AFL pioneered. Honggfuzz was developed at Google and open-sourced in 2015; it took a different architectural path from AFL from the start. Where AFL built its feedback loop around a shared memory bitmap and a fork server, Honggfuzz uses Linux perf hardware counters and POSIX signal handlers to collect feedback and detect crashes. This choice has downstream consequences for every aspect of the two tools: persistence mode, sanitizer integration, multi-process scaling, and target compatibility.
AFL++ is the community fork of AFL, extended significantly from 2019 onward with a modular mutation pipeline, LTO instrumentation, QEMU and Frida modes for binary-only targets, and the REDQUEEN/CmpLog taint-inference engine. It inherits AFL's fork-server model but adds persistent mode as an opt-in for targets that can support it.
Instrumentation and Coverage Feedback
Instrumentation is where the two fuzzers diverge most sharply. AFL++ offers multiple instrumentation backends selectable at compile time:
- PCGUARD (default) — wraps LLVM's SanitizerCoverage to populate a 64 kB edge bitmap. Fast, low overhead, works with any Clang version.
- LTO (link-time) — instruments at link time with
afl-clang-lto, eliminating duplicate edge IDs and achieving the most accurate coverage map of any mode. Requires LLD as the linker. - GCC plugin — for targets where Clang is not an option. Slightly lower throughput than the Clang modes but functionally equivalent.
- QEMU / Frida / Unicorn — dynamic instrumentation for closed-source binaries, mobile targets, and firmware.
Honggfuzz supports two feedback sources simultaneously:
- Software instrumentation via
hfuzz-clang/hfuzz-gccwrappers — populates edge counters similar to AFL++'s PCGUARD mode. - Hardware coverage via
perf_event_open— collects branch targets from CPU performance counters without any binary modification. Effective for binary-only targets on Linux without the QEMU overhead. Not available in containers withoutCAP_PERFMON.
In practice, software-instrumented Honggfuzz and AFL++ PCGUARD are comparable in feedback quality for standard targets. AFL++ LTO has an edge on targets with significant function call inlining, where PCGUARD produces duplicate edge IDs that inflate the bitmap without adding information. If you have a complex C++ codebase with aggressive inlining, LTO mode is worth the additional build complexity.
Persistent Mode: Architecture Differences Matter
Persistent mode is the single biggest performance multiplier in both fuzzers — 10–100× over the fork model — and the two tools implement it differently.
Honggfuzz persistent mode uses the LLVMFuzzerTestOneInput interface (the same as libFuzzer) and handles process reset via a signal handler. The fuzzer sends SIGABRT between iterations and the handler resets internal state. This means Honggfuzz persistent-mode harnesses are source-compatible with libFuzzer harnesses — you can often compile the same .c file with hfuzz-clang and get a Honggfuzz target with no modifications:
#include <stdint.h>
#include <stddef.h>
#include "my_parser.h"
// Honggfuzz persistent mode entry point
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
parse_input(data, size);
return 0;
}
// Build with:
// hfuzz-clang -fsanitize=address fuzz_target.c my_parser.c -o fuzz_target
// honggfuzz -i seeds/ -- ./fuzz_target ___FILE___AFL++ persistent mode uses the __AFL_LOOP(N) macro, which replaces the outer while(1) read loop. The fork server checkpoints at __AFL_INIT() and the child reuses the same address space for up to N iterations before forking a fresh copy:
#include "afl-fuzz.h"
#include "my_parser.h"
int main(void) {
uint8_t buf[65536];
ssize_t len;
__AFL_INIT(); // fork server checkpoint
while (__AFL_LOOP(1000)) {
len = read(STDIN_FILENO, buf, sizeof(buf));
if (len > 0) parse_input(buf, (size_t)len);
}
return 0;
}
// Build with:
// AFL_USE_ASAN=1 afl-cc -o fuzz_target fuzz_target.c my_parser.c
// afl-fuzz -i seeds/ -o findings/ -- ./fuzz_targetAFL++'s approach is slightly faster for targets with expensive initialization (the fork happens after init so the initialized state is inherited by each worker). Honggfuzz's signal-handler approach is simpler to adopt if you already have a libFuzzer harness. For new harnesses, the two are interchangeable.
Mutation Strategies
Mutation-based fuzzing effectiveness depends on the quality and diversity of the mutator set. AFL++ ships a richer mutation pipeline:
- Deterministic passes — bit flips, byte flips, arithmetic adds/subs, known integers (0, 1, -1, INT_MAX, etc.) applied sequentially to every byte offset.
- Havoc — random combinations of multiple mutation types applied in sequence. The workhorse stage for most targets.
- Splice — grafts a random section from one corpus entry into another. Effective for inputs with structured segments.
- CmpLog / REDQUEEN — taint-inference that tracks comparison operands and injects the exact bytes needed to satisfy comparisons. Dramatically effective against targets with checksums, magic bytes, or length fields.
- Custom mutators — pluggable via
AFL_CUSTOM_MUTATOR_LIBRARY. Use this for protocol-aware mutation (e.g., a grammar-based mutator for JSON or TLS records).
Honggfuzz's mutation engine is simpler: a set of byte-level mutations (bit flips, byte replacements, block operations, dictionary insertions) applied via a feedback-weighted scheduler. There is no built-in taint inference equivalent to CmpLog. For targets with hard comparison predicates (checksums, magic bytes), AFL++ with CmpLog enabled will generally find bugs faster.
Where Honggfuzz compensates is in the scheduling of corpus entries. Its feedback weighting algorithm tends to explore input space more uniformly, while AFL++'s havoc stage can spend disproportionate time on entries that happened to produce large coverage gains early in the campaign. For long-running campaigns (24+ hours), the difference usually disappears as both fuzzers exhaust the easy paths.
Multi-Binary and Stateful Target Fuzzing
Honggfuzz has a distinct advantage for targets that cannot be reduced to a single binary input function. Its network fuzzing mode (--net_server /--net_client) drives a server process by mutating what a client sends over a socket, collecting coverage from the server process even though input injection happens through the client. This is useful for stateful protocol implementations where the fuzz surface is a TCP/UDP stream, not a file:
# Honggfuzz multi-binary socket fuzzing
# Fuzz a server by driving a client
honggfuzz \
--input seeds/ \
--workspace findings/ \
--net_server \
--port 4321 \
-- ./my_server --port 4321
# In a second terminal: drive the server with mutated inputs
# honggfuzz handles the client automatically in --net_server modeAFL++ handles stateful targets via its AFLNet mode (a separate project built on top of AFL++) or via custom mutators that model protocol state. The setup is more complex than Honggfuzz's built-in network mode, but AFL++'s richer mutation pipeline often compensates for the extra configuration work.
Sanitizer Integration
Both fuzzers integrate cleanly with ASan, UBSan, and MSan. The build-time flags are the same: compile with -fsanitize=address,undefined and the sanitizers fire on the instrumented binary regardless of which fuzzer drives it.
The difference is in how each fuzzer interacts with sanitizer crash signals. AFL++ uses ASAN_OPTIONS=abort_on_error=1 by default, which causes ASan to raise SIGABRT on error, which AFL++ catches as a crash. Honggfuzz catches crashes via its own signal handler, which intercepts SIGSEGV, SIGABRT, SIGBUS, and SIGFPE. Both approaches work correctly. The practical implication: when you reproduce a Honggfuzz crash with an AFL++-built binary, set ASAN_OPTIONS=abort_on_error=1 explicitly; the Honggfuzz build may not have set it.
MSan is supported by both but requires the same all-or-nothing instrumentation constraint: every library must be MSan-instrumented. Neither fuzzer works around this limitation.
AFL++ LTO and Instrumentation Modes
# AFL++ LTO (link-time instrumentation) — best coverage accuracy
AFL_USE_ASAN=1 CC=afl-clang-lto CXX=afl-clang-lto++ \
./configure --disable-shared
make -j$(nproc)
# AFL++ PCGUARD (Clang SanitizerCoverage — default)
AFL_USE_ASAN=1 CC=afl-clang-fast CXX=afl-clang-fast++ \
./configure --disable-shared
make -j$(nproc)
# AFL++ QEMU mode (no source needed)
afl-fuzz -Q -i seeds/ -o findings/ -- ./prebuilt_binary @@For most targets, afl-clang-fast (PCGUARD) is the right starting point: it compiles faster than LTO and the coverage quality is adequate for targets where function inlining is moderate. Switch to afl-clang-lto if you notice the coverage bitmap saturating early (AFL++ reports map density above 70%) or if the target is a large C++ codebase with heavy inlining. LTO builds take longer but produce the most accurate edge IDs and tend to find more bugs per CPU-hour on complex targets.
CI Integration Patterns
# Honggfuzz in GitHub Actions
- name: Run honggfuzz (60 s)
run: |
honggfuzz -i seeds/ -o findings/ --timeout 60 \
--exit_upon_crash -- ./fuzz_target ___FILE___
ls findings/HONGGFUZZ.REPORT.* && exit 1 || exit 0
# AFL++ in GitHub Actions
- name: Run AFL++ (60 s)
run: |
timeout 60 afl-fuzz -i seeds/ -o out/ -V 55 -- ./fuzz_target @@ || true
ls out/default/crashes/id:* 2>/dev/null && exit 1 || exit 0Honggfuzz is slightly easier to integrate into CI because it writes a structured report file (HONGGFUZZ.REPORT.*) that is easy to check for in a shell script. AFL++ requires checking for files in the crashes/ directory. Both integrate well with corpus caching via actions/cache or external object storage.
One practical CI difference: Honggfuzz exits cleanly when the timeout is reached even if the --timeout flag was not set (it responds to SIGTERM). AFL++ requires -V <seconds> or a timeout wrapper to bound the run. Use -V rather than timeout when possible because AFL++ can clean up its shared memory segments properly when it exits normally.
Performance on Common Target Classes
Execution throughput depends heavily on the target, but some patterns are consistent across campaigns:
- File format parsers (image, audio, document): AFL++ and Honggfuzz are comparable. AFL++ CmpLog often finds bugs faster when the format has magic bytes or internal checksums.
- Network protocol implementations: Honggfuzz's built-in network mode has a clear ergonomic advantage. Raw throughput is similar once the target is wired up.
- Binary-only / closed-source targets: AFL++ QEMU mode is more mature and better documented than Honggfuzz's hardware coverage mode for containers without
CAP_PERFMON. - Large stateful C++ codebases: AFL++ LTO tends to achieve better coverage depth than Honggfuzz's software instrumentation on these targets due to more accurate edge IDs.
Recommendation Summary
- New target, libFuzzer harness already written: start with Honggfuzz (harness is source-compatible) then add AFL++ for CmpLog.
- Target has checksums, magic bytes, or complex predicates: AFL++ with CmpLog/REDQUEEN enabled.
- Stateful server / network protocol target: Honggfuzz
--net_servermode first. - Binary-only target, no source: AFL++ QEMU mode.
- Large C++ codebase with heavy inlining: AFL++ LTO mode.
- Maximum bug discovery over a long campaign: run both and merge corpora — each explores the input space differently.
- Constrained CI budget (<5 min per run): either; Honggfuzz exits more cleanly on timeout.
The honest answer is that neither consistently outperforms the other across all targets. The most effective fuzzing campaigns run AFL++ and Honggfuzz in parallel from the same seed corpus, letting each explore with its own mutation strategy and combining their corpus entries at the end of each campaign cycle.