Testland
Browse all skills & agents

afl-plus-plus

Author and run AFL++ - out-of-process coverage-guided fuzzer (a community fork of Google's original AFL with improved mutations and instrumentation). Covers afl-cc / afl-clang-fast instrumented build, afl-fuzz invocation, parallel master/slave (-M / -S), dictionary support (-x), QEMU mode (-Q) for binaries without source, output structure (queue / crashes / hangs), crash minimisation (afl-tmin), corpus minimisation (afl-cmin), crash filename triage, and CI integration. Use for fuzzing standalone binaries (file processors, command-line tools) where libFuzzer's in-process model doesn't fit; for cross-fuzzer corpus strategy see corpus-management-reference.

Install with skills.sh (any agent)

npx skills add testland/qa --skill afl-plus-plus
View source

afl-plus-plus

Overview

Distinct from libFuzzer (in-process) - AFL++ (per github.com/AFLplusplus/AFLplusplus (opens in new window)) fits binaries you can't compile-in (closed-source, multi-language) and tools that process inputs end-to-end (parsers reading files, decoders reading stdin).

For sanitiser pairing see sanitiser-integration-reference; for corpus discipline see corpus-management-reference.

When to use

  • Fuzzing a file-format parser whose API isn't directly callable.
  • Targeting a binary you don't have source for (QEMU mode).
  • Parallel campaigns across many cores via master/slave.
  • Comparison fuzzing - different mutation strategies catch different bugs than libFuzzer.

Authoring

Install

Per AFL++ README:

# Docker
docker pull aflplusplus/aflplusplus
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus

# Or apt (Debian/Ubuntu)
apt-get install -y afl++

For local build see docs/INSTALL.md in the AFL++ repo.

Instrument the target

CC=afl-cc CXX=afl-c++ ./configure --disable-shared
make clean all

Or LLVM-based (recommended):

CC=afl-clang-fast CXX=afl-clang-fast++ make

afl-clang-fast produces ~3x faster instrumented binaries than the legacy afl-cc.

Add sanitisers in the standard way:

AFL_USE_ASAN=1 AFL_USE_UBSAN=1 CC=afl-clang-fast \
  CFLAGS="-fno-sanitize-recover=all -fno-omit-frame-pointer -g" \
  make

AFL_USE_ASAN + AFL_USE_UBSAN env vars pass the sanitiser flags through AFL's compiler driver.

Running

Basic invocation

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

Per AFL++ README:

  • -i seeds/ - input seed corpus
  • -o output/ - output directory (created if absent)
  • -- ./target @@ - target binary; @@ is replaced by AFL with the input filename
  • For stdin-driven targets, omit @@: -- ./target (AFL pipes the input)

Common flags

FlagEffect
-M nameMaster process in parallel campaign
-S nameSecondary process (slave)
-x dict.txtUse dictionary
-t NPer-input timeout in ms (default 1000)
-m NMemory limit per child (MB)
-QQEMU mode for non-instrumented binaries
-c pathCMPLog binary (companion mode for collisions)
-d"Quick" mode - deterministic mutations skipped

Parallel fuzzing

# Terminal 1 - master
afl-fuzz -i seeds/ -o output/ -M main -- ./target @@

# Terminals 2..N - slaves
afl-fuzz -i seeds/ -o output/ -S slave1 -- ./target @@
afl-fuzz -i seeds/ -o output/ -S slave2 -- ./target @@

The master runs deterministic mutations; slaves run random mutations. Output corpus is shared via the output/ directory.

QEMU mode

For closed-source binaries:

afl-fuzz -Q -i seeds/ -o output/ -- ./closed_source_target @@

QEMU instruments via dynamic binary translation. ~5x slower than native instrumentation but works on any binary.

Parsing results

Output directory structure:

output/
  fuzzer_stats           # current run metrics
  fuzzer_setup           # configuration
  plot_data              # gnuplot-friendly time series
  queue/
    id:000000,orig:seed1.bin
    id:000001,src:000000,op:...     # mutated from id 0 with op
    ...
  crashes/
    id:000000,sig:11,src:000123,op:havoc,rep:8
    README.txt
  hangs/
    id:000000,...

Crash filename encoding (per AFL++ docs):

  • id:N - sequence number
  • sig:S - signal (11=SEGV, 6=SIGABRT, 9=SIGKILL, etc.)
  • src:M - derived from queue entry M
  • op:X - mutation operator that produced it
  • rep:R - repetition count

Reproduce a crash:

./target output/default/crashes/id:000000,sig:11,...
# Or for stdin-driven:
./target < output/default/crashes/id:000000,sig:11,...

For automated bug filing, parse the sanitiser output (if compiled with AFL_USE_ASAN=1) via bug-report-from-failure (in the qa-defect-management plugin).

Triaging crashes

afl-tmin minimises a single crash input:

afl-tmin -i output/default/crashes/id:000000,sig:11,... \
         -o minimised.bin \
         -- ./target @@

afl-cmin minimises the entire queue (corpus minimisation):

afl-cmin -i output/default/queue/ -o minimised_queue/ -- ./target @@

CI integration

- name: Install AFL++
  run: docker pull aflplusplus/aflplusplus
- name: Build instrumented target
  run: |
    docker run -v $PWD:/src aflplusplus/aflplusplus bash -c \
      "cd /src && CC=afl-clang-fast AFL_USE_ASAN=1 make"
- name: Smoke fuzz (5 min)
  run: |
    timeout 300 docker run -v $PWD:/src aflplusplus/aflplusplus \
      afl-fuzz -i /src/seeds -o /src/output -- /src/target @@ || true
- name: Upload crashes
  uses: actions/upload-artifact@v4
  with:
    name: afl-crashes
    path: output/default/crashes/

Anti-patterns

Anti-patternWhy it failsFix
Target compiled without AFL instrumentationNo coverage signal; fuzzer is blindUse afl-clang-fast or -Q (QEMU)
-i - (continuation) without checking output stateHard to resume; lost queue progressUse canonical -i seeds/ on fresh runs
Single AFL slave on a multi-core machine80% of cores idleRun N-1 slaves in parallel
No AFL_USE_ASAN=1Only catches crashes, not memory bugsAlways set sanitiser env vars
Treating hangs/ as bugsOften false positives (slow targets, infinite loops in test data)Investigate; raise -t for slow targets
Long-running campaign without afl-cmin cycleQueue bloats; cycle time degradesRun afl-cmin weekly
Mixing AFL++ + libFuzzer corporaFormat incompatibilityConvert via afl-fuzz -i corpus -E ... round-trip

Limitations

  • Out-of-process overhead. Fork-per-input is slower than libFuzzer's in-process iteration (~100-1000 execs/sec vs 10,000+).
  • Persistent mode partially mitigates. LLVMFuzzerTestOneInput-like persistent harnesses approach libFuzzer speed but require source instrumentation.
  • Queue format isn't libFuzzer-compatible. Cross-fuzzer corpus sharing requires conversion.
  • hangs/ reports many false positives. Slow but valid inputs sit there until manually triaged.
  • QEMU mode is slow (~5x). Use only when source isn't available.
  • Linux-first. macOS / Windows support exists but less mature.

References

Related skills

atheris-python-fuzzing

Author and run Atheris - Google's Python coverage-guided fuzzer built on libFuzzer. Covers pip installation, atheris.Setup + atheris.Fuzz invocation, TestOneInput(data: bytes) target signature, FuzzedDataProvider for structured input, instrument_imports() / instrument_func decorators for coverage instrumentation, and libFuzzer-passthrough flags (-atheris_runs, -max_total_time, -dict). Use for fuzzing Python libraries - also supports CPython native-extension fuzzing.

cargo-fuzz-rust

Author and run cargo-fuzz - Rust fuzzing via libFuzzer with cargo integration. Covers `cargo install cargo-fuzz`, `cargo fuzz init` + `cargo fuzz add {target}` for harness scaffolding, the `fuzz_target!` macro for entry-point declaration, the `Arbitrary` trait for structured input mutation, and `cargo fuzz run` invocation. Requires Rust nightly. Use for fuzz testing Rust libraries - cargo-fuzz wraps libFuzzer with native Rust ergonomics.

corpus-management-reference

Pure-reference catalog of fuzz-corpus management practices. Defines what a corpus is (seed corpus + evolved corpus saved by the fuzzer), corpus directory layout per libFuzzer / AFL++ / Go native / cargo-fuzz / OSS-Fuzz, the canonical crash-artefact naming (crash-{sha1} / leak-{sha1} / timeout-{sha1}), seed corpus construction strategies (sample-from-prod, sample-from-test-fixtures, from-spec-keywords), corpus minimisation, dictionary files, and the OSS-Fuzz integration corpus sync. Use as the corpus-discipline reference when building a fuzz target or maintaining a long-running fuzz campaign.

crash-triage-reference

Pure-reference catalog for manually triaging individual fuzzer crash artifacts - reading ASan, UBSan, and MSan output; classifying findings as LIKELY-EXPLOITABLE, MEDIUM, or BENIGN; deduplicating by stack-hash; and minimizing reproducers with -minimize_crash. Use when you need to understand what a specific crash means, build exploitability intuition, or manually work a small set of findings. For automated bulk triage across a full artifact directory, run automated findings triage instead.

fuzz-tool-selector

Routes a fuzz-target authoring task to the right fuzzer for the detected language and build type. Decision tree: C/C++ → libfuzzer-cpp + afl-plus-plus; Rust → cargo-fuzz-rust (or libfuzzer-cpp via FFI); Go → go-native-fuzzing; Python → atheris-python-fuzzing; JVM → jazzer-jvm-fuzzing; closed-source binary → afl-plus-plus in QEMU mode; mature open-source project → ossfuzz-integration. Use when a project needs coverage-guided fuzzing and no fuzzer has been chosen for its language or toolchain yet.

go-native-fuzzing

Author and run Go's native fuzzing (Go 1.18+) - coverage-guided fuzzing built into the standard testing package via FuzzXxx functions. Covers f.Add seed-corpus declaration, f.Fuzz callback signature with typed parameters, testdata/fuzz/{FuzzXxx}/ directory layout for seeds + regression cases, the -fuzz flag for `go test`, and CI integration via short smoke runs. Use for fuzz testing Go libraries - Go's native approach integrates seamlessly with standard `go test` rather than requiring a separate toolchain like AFL++.

jazzer-jvm-fuzzing

Author and run Jazzer - Code Intelligence's JVM coverage-guided fuzzer built on libFuzzer. Covers Maven / Gradle / standalone JAR installation, the @FuzzTest annotation (JUnit 5 integration), typed parameter mutation (String, primitives, byte[]), built-in JVM sanitisers (SSRF / path traversal / OS command injection / deserialization gadget / ReDoS), and the JAZZER_FUZZ=1 env var to switch between regression and fuzzing modes. Use for fuzz testing Java / Kotlin libraries - particularly effective against parsing, deserialization, and HTTP-handling code.

libfuzzer-cpp

Author and run LLVM libFuzzer for C/C++ - in-process coverage-guided fuzzing. Covers harness authoring (LLVMFuzzerTestOneInput entry point), build with -fsanitize=fuzzer,address,undefined, runtime flags (-max_total_time, -runs, -dict, -fork, -workers), corpus + crash-artefact handling, and CI integration. Use for libraries / parsers / decoders in C/C++ where in-process fuzzing of a function is the right scope. Compose with ASan + UBSan from sanitiser-integration-reference and corpus discipline from corpus-management-reference.

ossfuzz-integration

Author and submit a project to Google OSS-Fuzz - the open-source continuous fuzzing service that runs libFuzzer / AFL++ / Honggfuzz campaigns on Google infrastructure 24x7. Covers the project.yaml + Dockerfile + build.sh contract, the $OUT/$WORK conventions, supported languages + sanitisers, seed-corpus + dictionary submission, the OSS-Fuzz Build Status dashboard, and the disclosure SLA (issues filed in Monorail with 90-day deadline). Use to offload long-running fuzz campaigns to dedicated infrastructure rather than self-hosting.

sanitiser-integration-reference

Pure-reference catalog of compiler sanitisers used with fuzz testing - AddressSanitizer (ASan), UndefinedBehaviorSanitizer (UBSan), MemorySanitizer (MSan), ThreadSanitizer (TSan), and LeakSanitizer (LSan). Explains what each detects, compatibility (can ASan + UBSan combine? - yes; ASan + MSan? - no), build flags, runtime options (ASAN_OPTIONS / UBSAN_OPTIONS env vars), and the typical ~2x slowdown per ASan. Use to pick the right sanitiser per fuzz target, configure the build, and interpret crash reports.