Documentation Index
Fetch the complete documentation index at: https://mintlify.com/AFLplusplus/AFLplusplus/llms.txt
Use this file to discover all available pages before exploring further.
This guide covers battle-tested strategies, performance optimizations, and solutions to common fuzzing challenges.
Maximum Speed Configuration
Follow these steps in order for the best fuzzing performance:
Use LTO Mode Instrumentation
export CC=afl-clang-lto
export CXX=afl-clang-lto++
export AR=llvm-ar
export RANLIB=llvm-ranlib
./configure --disable-shared
make
LTO provides the best instrumentation quality and performance.Enable Persistent Mode
Rewrite your harness to use persistent mode:__AFL_FUZZ_INIT();
int main() {
setup();
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
fuzz_target(buf, len);
}
}
This provides 2-20x speed increase. See Persistent Mode. Use tmpfs for AFL_TMPDIR
export AFL_TMPDIR=/dev/shm
Reduces disk I/O and wear on SSDs.Disable CPU Mitigations
sudo afl-persistent-config
Or manually edit /etc/default/grub:GRUB_CMDLINE_LINUX_DEFAULT="ibpb=off ibrs=off kpti=off l1tf=off mds=off mitigations=off no_stf_barrier noibpb noibrs nopcid nopti nospec_store_bypass_disable nospectre_v1 nospectre_v2 pcid=off pti=off spec_store_bypass_disable=off spectre_v2=off stf_barrier=off"
Then sudo update-grub && sudo reboot.This reduces system security. Only use on dedicated fuzzing machines.
Use ext2 with noatime
sudo mkfs.ext2 /dev/sdX
sudo mount -o noatime /dev/sdX /mnt/fuzzing
ext2 is faster than journaling filesystems for fuzzing workloads.Run System Configuration
Run this before each fuzzing session for optimal system settings.
Selective Instrumentation
Instrument only the code you care about for significant speedups:
# Create allowlist
cat > allowlist.txt << EOF
parser.c
decoder.c
fun: process_input
fun: parse_header
EOF
export AFL_LLVM_ALLOWLIST=allowlist.txt
afl-clang-fast -o target target.c
Or use a denylist to exclude slow/irrelevant code:
# Create denylist
cat > denylist.txt << EOF
logging.c
debug.c
fun: print_stats
fun: hash_table_resize
EOF
export AFL_LLVM_DENYLIST=denylist.txt
afl-clang-fast -o target target.c
See Selective Instrumentation for details.
Stability Optimization
Understanding Stability
AFL++ reports stability as the percentage of consistent edge coverage across runs.
Good stability: 90-100% → Fuzzing efficiently
Poor stability: <90% → Losing coverage, slower progress
Identifying Unstable Edges
Enable Debug Output
export AFL_DEBUG=1
afl-fuzz -i input -o output -- ./target @@
After a few minutes, check output/fuzzer_stats for var_bytes - these are unstable edge IDs.Document Edge IDs
For LTO mode:export AFL_LLVM_DOCUMENT_IDS=/tmp/edge_ids.txt
afl-clang-lto -o target target.c
This creates a file mapping edge IDs to functions.For PCGUARD mode, modify instrumentation/afl-llvm-rt.o.c to log backtraces for specific edge IDs. Identify Problematic Functions
Match unstable edge IDs from var_bytes to functions in your documentation.Common culprits:
- Hash table resizing
- Random number generation
- Timestamp checks
- Jitter/timing code
- Thread synchronization
Exclude from Instrumentation
cat > denylist.txt << EOF
hash_map.c
fun: get_random
fun: check_timestamp
EOF
export AFL_LLVM_DENYLIST=denylist.txt
afl-clang-fast -o target target.c
Only exclude functions that don’t process fuzz data. If a function handles input (even indirectly), keep it instrumented despite instability.
Persistent Mode Stability
If persistent mode is unstable but non-persistent mode is stable:
// The target is keeping internal state!
// BAD:
static int call_count = 0; // Persists across iterations
void process(uint8_t *data, size_t len) {
call_count++; // State pollution
// ...
}
// GOOD:
void process(uint8_t *data, size_t len) {
int call_count = 0; // Reset each time
// ...
}
Solution: Reset all state between iterations or disable persistent mode.
Multi-Instance Strategy
Optimal Instance Distribution
For an 8-core machine:
export AFL_TESTCACHE_SIZE=100
export AFL_FINAL_SYNC=1
# Main instance (1 core)
afl-fuzz -M main-$HOSTNAME -i input -o output -- ./target @@
# CMPLOG instance (1 core)
afl-fuzz -S cmplog -c ./target-cmplog -l 2AT -i input -o output -- ./target @@
# Sanitizer instance (1 core)
AFL_USE_ASAN=1 AFL_USE_UBSAN=1 afl-fuzz -S asan -m none -i input -o output -- ./target-asan @@
# Secondary instances (5 cores)
afl-fuzz -S explore1 -p explore -P explore -i input -o output -- ./target @@
afl-fuzz -S fast1 -p fast -i input -o output -- ./target @@
afl-fuzz -S exploit1 -p exploit -P exploit -i input -o output -- ./target @@
AFL_DISABLE_TRIM=1 afl-fuzz -S notrim1 -p coe -i input -o output -- ./target @@
afl-fuzz -S mopt1 -L 0 -i input -o output -- ./target @@
Advanced Distribution (16+ cores)
Instance type percentages:
- 1 main instance with
AFL_FINAL_SYNC=1
- 1-2 CMPLOG instances (
-l 2AT or -l 2)
- 1-2 sanitizer instances (ASAN+UBSAN, MSAN)
- 10% MOpt mutator (
-L 0)
- 10% old queue cycling (
-Z)
- 50-70% with trim disabled (
AFL_DISABLE_TRIM=1)
- 40% explore power schedule (
-p explore -P explore)
- 20% exploit power schedule (
-p exploit -P exploit)
- Remaining: mix of
fast, coe, lin, quad, rare schedules
Example script for 32 cores:
#!/bin/bash
export AFL_TESTCACHE_SIZE=200
export AFL_FINAL_SYNC=1
# Main (1)
screen -dmS main -- afl-fuzz -M main-$HOSTNAME -i input -o output -- ./target @@
# CMPLOG (2)
screen -dmS cmplog1 -- afl-fuzz -S cmplog1 -c ./target-cmplog -l 2AT -i input -o output -- ./target @@
screen -dmS cmplog2 -- afl-fuzz -S cmplog2 -c ./target-cmplog -l 2 -i input -o output -- ./target @@
# Sanitizers (2)
screen -dmS asan -- afl-fuzz -S asan -m none -i input -o output -- ./target-asan @@
screen -dmS msan -- afl-fuzz -S msan -m none -i input -o output -- ./target-msan @@
# MOpt (3)
for i in {1..3}; do
screen -dmS mopt$i -- afl-fuzz -S mopt$i -L 0 -i input -o output -- ./target @@
done
# Old queue (3)
for i in {1..3}; do
screen -dmS old$i -- afl-fuzz -S old$i -Z -i input -o output -- ./target @@
done
# No trim (12)
export AFL_DISABLE_TRIM=1
for i in {1..12}; do
SCHED=(explore fast coe lin quad exploit rare)
S=${SCHED[$((i % 7))]}
screen -dmS notrim$i -- afl-fuzz -S notrim$i -p $S -i input -o output -- ./target @@
done
unset AFL_DISABLE_TRIM
# Regular (9)
for i in {1..9}; do
SCHED=(explore explore explore fast fast coe exploit exploit rare)
S=${SCHED[$((i - 1))]}
screen -dmS sec$i -- afl-fuzz -S sec$i -p $S -i input -o output -- ./target @@
done
Corpus Management
Initial Corpus Preparation
# 1. Collect diverse inputs
mkdir -p corpus_raw
# ... gather files from tests, examples, bug reports ...
# 2. Remove duplicates
afl-cmin -i corpus_raw -o corpus_unique -- ./target @@
# 3. Minimize individual files (optional but recommended)
mkdir corpus_minimized
cd corpus_unique
for f in *; do
afl-tmin -i "$f" -o "../corpus_minimized/$f" -- ../target @@
done
# 4. Use minimized corpus
afl-fuzz -i corpus_minimized -o output -- ./target @@
Adding Seeds Mid-Campaign
# Add new interesting seeds without stopping fuzzing
AFL_BENCH_JUST_ONE=1 AFL_FAST_CAL=1 \
afl-fuzz -i new_seeds -o output -S inject-seeds -- ./target @@
# Let it run for a few minutes, then Ctrl+C
# The main fuzzer will sync and distribute findings
Corpus Minimization During Fuzzing
# Periodically minimize the queue
afl-cmin -i output/main-*/queue -o queue_minimized -- ./target @@
# Replace queue (DANGEROUS - backup first!)
cp -r output/main-*/queue output/main-*/queue.backup
rm output/main-*/queue/*
cp queue_minimized/* output/main-*/queue/
Only minimize the queue if it becomes extremely large (>100,000 files) and fuzzing slows down significantly.
Dealing with Checksums and Validation
Removing Checksums from Source
Many file formats use checksums that prevent fuzzing:
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
// Skip checksum validation
return 1; // Pretend checksum is valid
#else
// Normal checksum verification
return verify_crc32(data, len, expected_crc);
#endif
All AFL++ compilers automatically define FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION.
Using Custom Mutators for Post-Processing
If you can’t modify source, use a custom mutator to fix checksums:
// custom_mutator.c
size_t afl_custom_post_process(void *data, unsigned char *buf,
size_t buf_size, unsigned char **out_buf) {
// Recalculate and fix checksum
uint32_t crc = calculate_crc32(buf, buf_size - 4);
memcpy(buf + buf_size - 4, &crc, 4);
*out_buf = buf;
return buf_size;
}
Compile and use:
afl-clang-fast -shared -o mutator.so custom_mutator.c
export AFL_CUSTOM_MUTATOR_LIBRARY=./mutator.so
afl-fuzz -i input -o output -- ./target @@
See Custom Mutators for complete guide.
Handling Special Target Types
dlopen() Instrumented Libraries
If your target loads instrumented libraries with dlopen() after the forkserver starts:
# Preload all libraries
export AFL_PRELOAD=lib1.so:lib2.so:lib3.so
afl-fuzz -i input -o output -- ./target @@
This ensures all instrumentation is present from the start.
PCGUARD and LTO instrumentation will abort() if dlopen()‘ed libraries aren’t preloaded. Use AFL_PRELOAD or switch to CLASSIC instrumentation.
Fuzzing Network Services
See dedicated Network Services Fuzzing Guide.
Quick tips:
- Modify source to read from files/stdin (best)
- Use socket emulation libraries (AFL_PRELOAD=libaflppdesock.so)
- Use AFLNet for stateful protocols
Fuzzing GUI Programs
See dedicated GUI Fuzzing Guide.
Quick tips:
- Extract and fuzz core logic without GUI
- Use Xvfb for headless execution
- Create harnesses that bypass UI entirely
Fuzzing Binary-Only Targets
See dedicated Binary-Only Fuzzing Guide.
Quick tips:
- Try ZAFL binary rewriter first (90-95% speed)
- Use QEMU/FRIDA persistent mode (3-8x faster than basic mode)
- Set AFL_ENTRYPOINT to skip initialization
Memory and Timeout Configuration
Setting Memory Limits
# Set 512MB limit
afl-fuzz -m 512 -i input -o output -- ./target @@
# No limit (for sanitizer builds)
afl-fuzz -m none -i input -o output -- ./target-asan @@
Set -m to 2-4x the maximum memory your seeds use. This helps detect missing malloc() failure handling.
Setting Timeouts
# 1 second timeout
afl-fuzz -t 1000 -i input -o output -- ./target @@
# Aggressive timeout for fast targets
afl-fuzz -t 5 -i input -o output -- ./target @@
Lower timeouts make fuzzing faster by skipping slow inputs:
- Fast targets (>5000 exec/s):
-t 5 to -t 20
- Medium targets (1000-5000 exec/s):
-t 50 to -t 200
- Slow targets (<1000 exec/s):
-t 1000+
CI/CD Fuzzing
Short fuzzing runs in continuous integration require different configuration:
#!/bin/bash
# CI fuzzing script (10-minute runs)
# Fast compilation
export CC=afl-clang-fast # NOT afl-clang-lto (too slow to compile)
export CXX=afl-clang-fast++
# Randomize features (30% CMPLOG, 5% LAF)
if [ $((RANDOM % 10)) -lt 3 ]; then
export AFL_LLVM_CMPLOG=1
elif [ $((RANDOM % 20)) -eq 0 ]; then
export AFL_LLVM_LAF_ALL=1
fi
./configure && make
# Fast calibration
export AFL_FAST_CAL=1
export AFL_CMPLOG_ONLY_NEW=1
# Randomize runtime options
OPTS="-i input -o output"
# 65% disable trim
[ $((RANDOM % 100)) -lt 65 ] && export AFL_DISABLE_TRIM=1
# 50% keep timeouts
[ $((RANDOM % 2)) -eq 0 ] && export AFL_KEEP_TIMEOUTS=1
# 40% expand havoc
[ $((RANDOM % 10)) -lt 4 ] && export AFL_EXPAND_HAVOC_NOW=1
# 10% MOpt
[ $((RANDOM % 10)) -eq 0 ] && OPTS="$OPTS -L 0"
# 20% old queue
[ $((RANDOM % 5)) -eq 0 ] && OPTS="$OPTS -Z"
# Run as secondary fuzzer (no -M)
timeout 600 afl-fuzz -S ci-$CI_JOB_ID $OPTS -- ./target @@
# Check for crashes
if [ -n "$(ls output/*/crashes/id:* 2>/dev/null)" ]; then
echo "CRASHES FOUND!"
exit 1
fi
Key CI differences:
- Use
afl-clang-fast (fast compile) instead of afl-clang-lto
- Set
AFL_FAST_CAL=1 and AFL_CMPLOG_ONLY_NEW=1
- Reuse corpus from previous runs with
afl-cmin
- Only use
-S instances (no -M)
- Randomize instrumentation and runtime options
Distributed Fuzzing
Syncing Across Machines
Sync findings between multiple servers:
#!/bin/bash
# sync_fuzzers.sh - Run every 4 hours
SERVERS=("server1" "server2" "server3")
PROJECT="target"
OUT_DIR="/fuzzing/$PROJECT/output"
for SRC in "${SERVERS[@]}"; do
for DST in "${SERVERS[@]}"; do
[ "$SRC" = "$DST" ] && continue
# Sync main instance queue
rsync -az --delete \
$SRC:$OUT_DIR/main-$SRC/queue/ \
$DST:$OUT_DIR/main-$SRC/queue/
done
done
Run via cron:
0 */4 * * * /path/to/sync_fuzzers.sh
See utils/distributed_fuzzing for advanced scripts.
Monitoring and Debugging
Check Fuzzing Status
# Detailed status
afl-whatsup output/
# Summary only
afl-whatsup -s output/
# Generate plots
afl-plot output/main-hostname /var/www/html/plot
Check Coverage
# Quick coverage check
afl-showmap -C -i output/main-*/queue -o /dev/null -- ./target @@
# Detailed line coverage
git clone https://github.com/vanhauser-thc/afl-cov
cd afl-cov
./afl-cov -d ../output --live --coverage-cmd "../target @@" --code-dir /path/to/source
Debug Crashes
# Reproduce crash
./target output/main-*/crashes/id:000000*
# With GDB
gdb --args ./target output/main-*/crashes/id:000000*
# With ASAN
ASAN_OPTIONS=symbolize=1:abort_on_error=1 ./target-asan output/main-*/crashes/id:000000*
# Minimize crash
afl-tmin -i output/main-*/crashes/id:000000* -o crash.min -- ./target @@
# Explore crash variants
afl-fuzz -C -i output/main-*/crashes -o crash_exploration -- ./target @@
Cluster Crashes with CASR
pip3 install casr
casr-afl -i output -o casr_reports
# View report
cat casr_reports/clustered/cl1/casr-report-*.casrep
Common Pitfalls and Solutions
”No instrumentation detected"
# Check if binary is instrumented
afl-showmap -o /dev/null -- ./target < input/seed
# Should show: "Captured X tuples"
# If 0 tuples, recompile with AFL++ compiler
"Suboptimal CPU scaling governor"
sudo afl-system-config
# Or manually:
sudo cpupower frequency-set -g performance
# Recompile AFL++ with larger map
export AFL_MAP_SIZE=10000000
make clean all
Fuzzing stops finding new paths
# 1. Check coverage
afl-showmap -C -i output/main-*/queue -o /dev/null -- ./target @@
# 2. If coverage is low, add better seeds
# 3. Try different mutators
# 4. Enable CMPLOG if not already
# 5. Consider switching to different fuzzer (libFuzzer, honggfuzz)
Very low stability (<70%)
See Stability Optimization section above.
Crashes don’t reproduce
# 1. Check memory limits
./target < output/main-*/crashes/id:000000*
# vs
afl-fuzz -m 512 ... (with same limit)
# 2. Check if persistent mode state pollution
# Recompile without persistent mode and test
# 3. Enable crash recording (slow!)
export AFL_ALLOW_CORES=1
afl-fuzz ...
# Crashes will generate core dumps
gdb ./target core
Advanced Techniques
Using AFL++ with libFuzzer Corpus
# Minimize libfuzzer corpus
afl-cmin -i libfuzzer_corpus -o afl_input -- ./target @@
# Fuzz with AFL++
afl-fuzz -i afl_input -o output -- ./target @@
# Sync back to libfuzzer
cp output/main-*/queue/* libfuzzer_corpus/
Parallel Fuzzing with honggfuzz
# Terminal 1: AFL++
afl-fuzz -M afl-main -i input -o shared_output -- ./target @@
# Terminal 2: honggfuzz
honggfuzz -i input -W shared_output -n 1 -- ./target ___FILE___
# They will sync automatically
Differential Fuzzing
// Compare two implementations
__AFL_FUZZ_INIT();
int main() {
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
int result1 = impl1_parse(buf, len);
int result2 = impl2_parse(buf, len);
if (result1 != result2) {
abort(); // Difference found!
}
}
}
Next Steps