BLOG
BLOG
That is not a failure of fuzzing. It is a failure of interpretation.
In a recent AFL++ fuzzing campaign targeting libarchive, we ran approximately 8.5 billion executions across all fuzzing phases, generated over a thousand crash files, and ultimately reduced them to two unique crash sites through structured crash triage and deduplication.
This blog is a practical, engineering-first guide to that process:
If your fuzzing pipeline stops at crash counts, you are not measuring security. You are measuring noise.
Modern fuzzers like AFL++ are extremely good at generating output. That output, however, is not directly equivalent to vulnerabilities.
This is why:
If you do not implement fuzzing deduplication, and root cause clustering, your results will always be inflated.
libarchive is an ideal fuzzing target because:
This creates a realistic attack surface where malformed inputs can trigger memory safety issues, null dereferences, parser inconsistencies, and denial-of-service conditions.
Before running AFL++, the most critical step is building the correct binaries.
Minimum viable AFL++ setup:
|
Binary |
Purpose |
|
Native (LTO) |
Maximum throughput |
|
ASAN |
Memory error detection |
|
CmpLog |
Unlocks comparison-based paths |
LTO instrumentation provides full-program visibility, collision-free edge coverage, and automatic dictionary extraction.
ASAN introduces approximately two times the runtime overhead. Running all instances with ASAN reduces total executions and limits discovery.
Correct pattern
afl-fuzz -M main ... -w ./target_asan -- ./target_native
The native binary delivers speed. The ASAN binary validates memory safety.
Many formats rely on strict comparisons and magic bytes. CmpLog allows AFL++ to observe runtime comparisons, extract operand values, and inject them into mutations. This significantly improves path discovery in structured formats.
Start simple.
Instead of building harnesses immediately, fuzz the CLI target:
afl-fuzz -i afl_inp -o afl_out/ -t 1000 -M FUZZ01_LTO \
-- ./lto_build/bin/bsdtar -xf @@ -C /tmp/out_bsdtar
Do not skip validation. Broken setups scale poorly.
The most important performance improvement in AFL++ is persistent mode.
while (__AFL_LOOP(10000)) {
archive_read_open_memory(a, buf, len);
archive_read_next_header(...);
}
Once persistent mode removes execution overhead, the next bottleneck is how effectively multiple fuzzing instances explore the input space.
The key secondary-side choice is the power schedule (-p), and the rule is simple: don’t give every secondary the same one.
If all instances run identical schedules, they quickly converge on similar mutations, leading to redundant work and poor CPU utilization. Mixed schedules ensure each instance explores a different region of the search space.
This diversity ensures that parallel fuzzers are complementary rather than duplicative.
One secondary instance should run with -L 0 to enable MOpt (Mutation Operator Optimization).
MOpt uses a particle swarm optimization model to:
It performs best as a single adaptive instance within a heterogeneous setup, not as a replacement for all fuzzers.
Persistent mode unlocks throughput.
The parallel strategy determines whether that throughput translates into meaningful coverage growth or wasted cycles.
This phase builds coverage, not bugs.
Once coverage stabilizes, shift the objective.
Coverage plateaued while crash volume increased. This indicates duplication rather than new bug discovery.
More speed increases crash volume, not necessarily the number of new vulnerabilities.
To find new bugs, you must change the surface being tested.
These areas introduce pointer-linked structures, count mismatches, and deep iteration paths.
Malformed inputs here trigger:
Expanding the surface alone is insufficient. Without a diversified mutation strategy, parallel fuzzers converge on similar paths and waste cycles.
Phase 4 introduces explicit power-schedule separation across secondaries, ensuring that each instance explores a distinct region of the input space.
Same harness + same schedule = redundant work
Same harness + different schedules = parallel exploration
Each instance:
This is what allows deeper surfaces to actually introduce new bugs, rather than duplicating earlier crash classes.
New crash classes emerged from deeper parser logic, not increased iteration volume.
The sharp increase in hangs reflects:
Critically, these were new execution regions, not extensions of earlier write-path bugs.
New bugs come from:
New surfaces × Diverse mutation strategies
Not more iterations.
Surface expansion without schedule diversity produces redundancy, not discovery.
|
Stage |
Files |
|
Initial seeds |
29 |
|
Post CLI |
42 |
|
Post Phase 2 |
1,059 |
|
Post Phase 4 |
6,779 |
|
Final merged |
36,310 |
Raw corpus growth is exponential. Unique coverage is not.

This funnel represents the most important concept in fuzzing at scale. Large volumes of crashes collapse into a very small number of real issues.
Fuzzing produces crashing inputs. It does not directly produce vulnerabilities.
|
Stage |
Count |
|
Raw crashes |
1,166 (approx) |
|
Reproducible |
357 |
|
Unique crash sites |
2 |
Both bugs were located in:
archive_entry_sparse.c
If a fuzzing campaign produces hundreds of crashes, more than ninety percent are typically duplicates.
Crash triage is where fuzzing becomes engineering.
To replicate this workflow, start with:
Expand only after stability is confirmed.
This is not just a fuzzing problem. It reflects a broader failure in application security pipelines:
Security tools identify where systems break. Engineering determines what actually matters.
This is the shift toward execution-aware security:
Fuzzing produces noise. Engineering produces signal.
The difference is everything.
A well-run fuzzing workflow should:
If your pipeline ends at 357 crashes, it is incomplete.
If it ends at 2 root causes, it is useful.
AFL++ is a coverage-guided fuzzing framework used to discover vulnerabilities by mutating inputs and observing program behavior.
Fuzzers generate many crashes because the same bug can be triggered through multiple execution paths.
You can deduplicate fuzzing crashes by using clustering tools such as CASR, which group crashes based on stack traces.
A persistent mode in AFL++ is one in which the target runs in a loop, avoiding process restarts and improving performance.
The crash count is misleading because it reflects detection frequency rather than unique vulnerabilities.
Hackers never rest. Neither should your security!
Stay ahead of emerging threats, vulnerabilities, and best practices in mobile app security—delivered straight to your inbox.
Exclusive insights. Zero fluff. Absolute security.
Join the Appknox Security Insider Newsletter!