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.
When you have access to source code, AFL++ provides highly efficient instrumentation-based fuzzing. This guide walks through the complete workflow from compilation to running a fuzzing campaign.
Overview
Source code fuzzing involves three main phases:
- Instrumentation - Compile the target with AFL++ compilers to inject coverage tracking
- Corpus Preparation - Collect and optimize input test cases
- Fuzzing Campaign - Run AFL++ to discover bugs and crashes
Fuzzing puts significant strain on hardware. CPUs will run hot, disk I/O will be heavy, and memory usage can spike. Ensure adequate cooling and monitor system resources. See Common Sense Risks for details.
Step 1: Instrumenting the Target
Choosing the Right Compiler
AFL++ provides multiple instrumentation modes. Select the best one for your environment:
LTO Mode (Recommended)
Use afl-clang-lto if you have LLVM/Clang 11+. This provides the best instrumentation and performance.CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --disable-shared
make
For LTO mode, also set:AR=llvm-ar RANLIB=llvm-ranlib
See LTO Mode Instrumentation for full details. LLVM Mode (Fallback)
Use afl-clang-fast if you have LLVM/Clang 3.8+ but LTO fails:CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --disable-shared
make
See LLVM Mode Instrumentation for details. GCC Plugin Mode (Last Resort)
Use afl-gcc-fast if you have GCC 5+ but no suitable LLVM:CC=afl-gcc-fast CXX=afl-g++-fast ./configure --disable-shared
make
See GCC Plugin Mode for details.
Always compile libraries statically when fuzzing. Avoid instrumenting shared libraries as they require LD_LIBRARY_PATH setup and can cause system-wide issues if accidentally installed.
Advanced Instrumentation Options
CMPLOG (Recommended)
CMPLOG instruments comparison operations to help AFL++ solve magic bytes and checksums:
# Compile a separate CMPLOG binary
export AFL_LLVM_CMPLOG=1
afl-clang-fast -o target-cmplog target.c
# Compile normal binary
unset AFL_LLVM_CMPLOG
afl-clang-fast -o target target.c
# Use with -c flag during fuzzing
afl-fuzz -i input -o output -c target-cmplog -- ./target @@
See CMPLOG Instrumentation for more details.
LAF-Intel / COMPCOV
Splits complex comparisons to make them easier to solve:
export AFL_LLVM_LAF_ALL=1
afl-clang-fast -o target target.c
LAF-Intel can significantly increase binary size and slow down execution. Use CMPLOG instead when possible.
Selective Instrumentation
Instrument only specific files or functions to reduce overhead:
Allowlist approach (instrument only these):
cat > allowlist.txt << EOF
parser.c
fun: process_input
EOF
export AFL_LLVM_ALLOWLIST=allowlist.txt
afl-clang-fast -o target target.c
Denylist approach (skip these from instrumentation):
cat > denylist.txt << EOF
logging.c
fun: debug_print
EOF
export AFL_LLVM_DENYLIST=denylist.txt
afl-clang-fast -o target target.c
See Selective Instrumentation for details.
Adding Sanitizers
Sanitizers detect bugs that don’t cause crashes. Run a few instances with sanitizers:
# AddressSanitizer (memory corruption)
export AFL_USE_ASAN=1
afl-clang-fast -o target-asan target.c
# UndefinedBehaviorSanitizer
export AFL_USE_UBSAN=1
afl-clang-fast -o target-ubsan target.c
# MemorySanitizer (uninitialized memory)
export AFL_USE_MSAN=1
afl-clang-fast -o target-msan target.c
Run only 1-2 instances with sanitizers in your fuzzing campaign. They’re slow (50-100x overhead) but effective at finding subtle bugs.
Persistent mode provides a 2-20x speed increase by reusing the same process:
#include <unistd.h>
__AFL_FUZZ_INIT();
int main(int argc, char **argv) {
// One-time initialization
setup();
__AFL_INIT();
unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF;
while (__AFL_LOOP(10000)) {
int len = __AFL_FUZZ_TESTCASE_LEN;
// Process input
fuzz_target(buf, len);
}
return 0;
}
See Persistent Mode for complete implementation guide.
libFuzzer Harnesses
AFL++ supports standard libFuzzer harnesses:
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Your fuzzing logic here
process_data(data, size);
return 0;
}
Compile and fuzz:
afl-clang-fast++ -fsanitize=fuzzer -o harness harness.cpp target.a
afl-fuzz -i input -o output -- ./harness
Step 2: Preparing the Corpus
A good seed corpus dramatically improves fuzzing efficiency.
Collect Input Files
Gather valid inputs from:
- Unit test data
- Regression test suites
- Example files from documentation
- Real-world samples from the internet
- Bug report attachments
Put all files in an INPUTS/ directory. Remove Duplicates with afl-cmin
Remove inputs that don’t add new coverage:# For file-based targets
afl-cmin -i INPUTS -o INPUTS_UNIQUE -- ./target @@
# For stdin-based targets
afl-cmin -i INPUTS -o INPUTS_UNIQUE -- ./target
This significantly speeds up fuzzing by removing redundant test cases. Minimize Individual Files (Optional)
Make each input as small as possible while keeping the same coverage:mkdir input
cd INPUTS_UNIQUE
for i in *; do
afl-tmin -i "$i" -o "../input/$i" -- ../target @@
done
This step is optional but improves fuzzing speed. Can be parallelized.
If you have no sample inputs, create a minimal valid input manually or use AFL++‘s included test cases in testcases/ directory.
Step 3: Running the Fuzzing Campaign
System Preparation
Configure System for Fuzzing
Run this before each fuzzing session to optimize system settings. Persistent Configuration (Optional)
sudo afl-persistent-config
Sets boot-time optimizations for maximum performance (reduces security).
Basic Single-Instance Fuzzing
# File-based target
afl-fuzz -i input -o output -- ./target @@
# Stdin-based target
afl-fuzz -i input -o output -- ./target
# With a dictionary
afl-fuzz -i input -o output -x dict.txt -- ./target @@
Production Multi-Core Fuzzing
Running only one fuzzing instance is ineffective. Use all available CPU cores for serious fuzzing campaigns.
Recommended setup for an 8-core machine:
# Set cache size for performance (optional)
export AFL_TESTCACHE_SIZE=100
# Main fuzzer instance
screen -dmS main -- afl-fuzz -M main-$HOSTNAME -i input -o output -- ./target @@
# CMPLOG instance
screen -dmS cmplog -- afl-fuzz -S cmplog -l 2AT -c ./target-cmplog -i input -o output -- ./target @@
# Sanitizer instance (ASAN)
export AFL_USE_ASAN=1
screen -dmS asan -- afl-fuzz -S asan -i input -o output -- ./target-asan @@
unset AFL_USE_ASAN
# Secondary instances with different strategies
screen -dmS explore1 -- afl-fuzz -S explore1 -p explore -i input -o output -- ./target @@
screen -dmS fast1 -- afl-fuzz -S fast1 -p fast -i input -o output -- ./target @@
screen -dmS exploit1 -- afl-fuzz -S exploit1 -p exploit -P explore -i input -o output -- ./target @@
screen -dmS mopt -- afl-fuzz -S mopt -L 0 -i input -o output -- ./target @@
screen -dmS old -- afl-fuzz -S old -Z -i input -o output -- ./target @@
Set AFL_FINAL_SYNC=1 for the main instance to enable final synchronization of all findings.
Recommended Instance Distribution
For optimal coverage, distribute instances as follows:
- 1 main instance (
-M) with AFL_FINAL_SYNC=1
- 1 CMPLOG instance with
-l 2AT
- 1 sanitizer instance (ASAN+UBSAN)
- 10% with MOpt (
-L 0)
- 10% with old queue cycling (
-Z)
- 50-70% with trim disabled (
AFL_DISABLE_TRIM=1)
- 40% with explore (
-p explore -P explore)
- 20% with exploit (
-p exploit -P exploit)
- Rest with different power schedules:
fast, coe, lin, quad, rare
See afl-fuzz Command Reference for all options.
Monitoring Fuzzing Progress
Check campaign status:
# Detailed status of all instances
afl-whatsup output/
# Summary only
afl-whatsup -s output/
# Generate web-based graphs
afl-plot output/main-hostname /var/www/html/plot
Adding New Seeds Mid-Campaign
AFL_BENCH_JUST_ONE=1 AFL_FAST_CAL=1 afl-fuzz -i newseeds -o output -S newseeds -- ./target @@
After a few minutes, terminate it. The main fuzzer will distribute new findings.
Checking Coverage
Measure actual code coverage achieved:
afl-showmap -C -i output -o /dev/null -- ./target @@
For detailed line-by-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
Use Persistent Mode
Rewrite your harness to use persistent mode for 2-20x speed increase.
Use tmpfs for AFL_TMPDIR
export AFL_TMPDIR=/dev/shm
Reduces disk I/O wear and improves speed.Disable CPU Mitigations
sudo afl-persistent-config
# Or manually edit /etc/default/grub
Warning: Reduces system security.Use ext2 Filesystem
Mount output directory on ext2 with noatime option for slight speed improvement.
Triaging Crashes
Minimize a crashing input:
afl-tmin -i output/default/crashes/id:000000* -o crash-min -- ./target @@
Cluster and analyze crashes with CASR:
casr-afl -i output -o casr-reports
Explore crash variations:
afl-fuzz -C -i output/default/crashes -o crash-exploration -- ./target @@
Common Pitfalls
Don’t instrument shared libraries - Always compile statically or you risk system corruption and poor performance.
Set memory limits - Use -m to prevent OOM crashes:afl-fuzz -m 512 -i input -o output -- ./target @@
Disable quiet builds - Some build systems break with AFL++ output:export AFL_QUIET=1
./configure
unset AFL_QUIET
make
Next Steps