Skip to main content
This guide walks you through fuzzing a target with source code available. For binary-only targets, see the Binary-only fuzzing documentation.
Before you start, be aware of common sense risks of fuzzing:
  • Your CPU will run hot and needs adequate cooling
  • Targets may grab gigabytes of memory or fill disk space
  • Heavy I/O can reduce SSD/HDD lifespan - consider using AFL_TMPDIR with a RAM-disk
  • Don’t fuzz on systems where data loss is unacceptable

Overview

Fuzzing with AFL++ involves three steps:
  1. Instrument the target - Compile with AFL++ compilers to add instrumentation
  2. Prepare seeds - Collect and optimize initial input corpus
  3. Run afl-fuzz - Execute the fuzzer to find bugs

Step 1: Instrument your target

Select the AFL++ compiler

Choose the best compiler for your setup:
1

LTO mode (best)

If you have clang/clang++ 11 or newer:
CC=afl-clang-lto CXX=afl-clang-lto++
See LTO mode documentation for details.
2

LLVM mode (good)

If you have clang/clang++ 3.8 or newer:
CC=afl-clang-fast CXX=afl-clang-fast++
See LLVM mode documentation for details.
3

GCC_PLUGIN mode (fallback)

If you have GCC 5+ with plugin support:
CC=afl-gcc-fast CXX=afl-g++-fast
See GCC_PLUGIN documentation for details.
It is highly recommended to have the newest LLVM version possible installed. Anything below LLVM 9 is not recommended.

Compile the target

For configure-based build systems:
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --disable-shared
make clean all
For CMake-based projects:
mkdir build && cd build
cmake -DCMAKE_C_COMPILER=afl-cc -DCMAKE_CXX_COMPILER=afl-c++ ..
make
For Meson build system:
CC=afl-cc CXX=afl-c++ meson
Important compilation tips:
  • Always compile libraries as static, not shared (use --disable-shared)
  • If the build system complains about stderr output, set export AFL_QUIET=1
  • If configure fails with AFL++ compiler, set export AFL_NOOPT=1 during configure only
  • Disable -Werror if the build system treats warnings as errors
CMPLOG helps solve complex comparisons and magic bytes:
# Compile a CMPLOG-instrumented binary
AFL_LLVM_CMPLOG=1 afl-clang-fast target.c -o target-cmplog

# Compile the regular fuzzing binary
afl-clang-fast target.c -o target
You’ll use the CMPLOG binary with the -c flag during fuzzing.

Advanced: Enable LAF-INTEL (alternative)

LAF-INTEL splits complex comparisons:
export AFL_LLVM_LAF_ALL=1
afl-clang-fast target.c -o target
CMPLOG is usually more effective than LAF-INTEL. Use LAF-INTEL when targets heavily transform input before comparison.

Step 2: Prepare seed inputs

Collect initial inputs

Gather valid input files for your target:
  • Example files from the target’s test suite
  • Sample files from bug reports
  • Files downloaded from the internet
  • Files generated by running the target normally
AFL++ includes example seed files in the testcases/ directory for common formats.

Create seeds directory

mkdir seeds
cp /path/to/valid/inputs/* seeds/

Optional: Minimize the corpus

Remove redundant inputs that don’t provide new coverage:
# If target reads from a file
afl-cmin -i seeds -o seeds_unique -- ./target @@

# If target reads from stdin
afl-cmin -i seeds -o seeds_unique -- ./target
The @@ symbol is replaced by AFL++ with the path to the test input file.

Optional: Minimize individual files

Shorter inputs fuzz faster:
mkdir input
cd seeds_unique
for i in *; do
  afl-tmin -i "$i" -o "../input/$i" -- /path/to/target @@
done
This step is optional but can improve fuzzing efficiency.

Step 3: Run afl-fuzz

System configuration

Before fuzzing, configure your system for optimal performance:
sudo afl-system-config
This command is required before the first fuzzing session after each reboot. If you can’t run it with root privileges, set export AFL_SKIP_CPUFREQ=1 to skip the check.
For permanent boot-time optimizations:
sudo afl-persistent-config
These optimizations improve fuzzing performance but decrease system security. Use strong firewall rules and only expose SSH if you apply these settings.

Basic fuzzing

Start fuzzing with the minimal configuration:
afl-fuzz -i seeds -o output -- ./target @@
Breaking this down:
  • -i seeds: Input directory with seed files
  • -o output: Output directory (created automatically)
  • --: Separates afl-fuzz options from target command
  • ./target: Your instrumented binary
  • @@: Placeholder for input filename (omit if target reads from stdin)

With CMPLOG

If you compiled a CMPLOG binary:
afl-fuzz -i seeds -o output -c ./target-cmplog -- ./target @@

With a dictionary

Dictionaries help with verbose formats (SQL, HTTP, etc.):
afl-fuzz -i seeds -o output -x /path/to/dictionary.txt -- ./target @@
AFL++ includes dictionaries for common formats in dictionaries/ directory.

Setting memory limits

Limit memory usage to prevent OOM and find malloc failures:
afl-fuzz -i seeds -o output -m 512 -- ./target @@
This sets a 512MB memory limit. Adjust based on your target’s needs.
afl-fuzz -i seeds -o output \
  -c ./target-cmplog \
  -x dictionaries/sql.dict \
  -m 512 \
  -- ./target @@

Understanding the UI

When afl-fuzz runs, you’ll see a status screen:
┌─────────────────────────────────────────────────────────┐
│ process timing │ overall results │ cycle progress     │
│ last new find  │ total crashes   │ map coverage       │
│ total execs    │ saved crashes   │ stability          │
│ exec speed     │ total tmouts    │ strategy info      │
└─────────────────────────────────────────────────────────┘
Investigate anything shown in red immediately. Consult the status screen documentation for details.
Key metrics:
  • last new find: Time since last new path was discovered
  • total crashes: Number of unique crashes found
  • exec speed: Executions per second (higher is better)
  • map coverage: Percentage of instrumentation points hit

Analyzing results

Finding crashes

Crashes are saved in the output directory:
ls output/default/crashes/
Each crash file is named with metadata: id:NNNNNN,sig:NN,src:NNNNNN,...

Reproducing crashes

Test a crash manually:
# If target reads from stdin
cat output/default/crashes/id:000000* | ./target

# If target reads from a file
./target output/default/crashes/id:000000*

Debugging crashes

Use GDB to investigate:
gdb ./target
(gdb) run < output/default/crashes/id:000000*
Or generate core dumps:
ulimit -c unlimited
./target < output/default/crashes/id:000000*
gdb ./target core

Finding hangs

Inputs that cause timeouts are saved in:
ls output/default/hangs/

Stopping and resuming

Stop fuzzing

Press Ctrl+C or send SIGINT to afl-fuzz.

Resume fuzzing

Reuse the same command but replace -i seeds with -i -:
afl-fuzz -i - -o output -- ./target @@
Alternatively, set AFL_AUTORESUME=1 to auto-resume:
export AFL_AUTORESUME=1
afl-fuzz -i seeds -o output -- ./target @@

Multi-core fuzzing

If you want to fuzz seriously, use multiple CPU cores. A single instance is just for hobby fuzzing!

Start the main fuzzer

afl-fuzz -i seeds -o output -M main -- ./target @@

Start secondary fuzzers

In separate terminals (or screen/tmux sessions):
afl-fuzz -i seeds -o output -S secondary1 -- ./target @@
afl-fuzz -i seeds -o output -S secondary2 -- ./target @@
afl-fuzz -i seeds -o output -S secondary3 -- ./target @@
Recommended variations for secondaries:
  • One with CMPLOG: -c target-cmplog -l 2AT
  • One with sanitizers (ASAN/UBSAN)
  • Different power schedules: -p fast, -p explore, -p exploit
  • Some with MOpt: -L 0
  • Some with old queue cycling: -Z

Check fuzzing status

Monitor all instances:
afl-whatsup output/

# Or just the summary
afl-whatsup -s output/

Next steps

This quickstart covers the basics. For professional fuzzing campaigns, read the complete fuzzing in depth guide.
To improve your fuzzing:
  1. Use persistent mode for 2-20x speed improvement
  2. Use libfuzzer harnesses with LLVMFuzzerTestOneInput()
  3. Implement custom mutators for domain-specific formats
  4. Enable sanitizers to find non-crashing bugs
  5. Read about best practices for network services and GUI programs
Resources:

Build docs developers (and LLMs) love