SkillAgentSearch skills...

aflpp

Use when the user needs to set up and run an AFL++ fuzzing campaign for a C/C++ target. Compiles with LTO, LLVM, or GCC instrumentation, runs single or multi-core campaigns, triages crashes, and minimizes the corpus. Requests explicit approval before system-level changes.

Install / Use

npx skills add OutlineDriven/outline-driven-development

Installs into whichever agent you are using.

About this skill
📄

SKILL.md

Installable skill definition

Quality Score

69/100

Supported Platforms

Universal

Tags


name: aflpp description: 'Use when the user needs to set up and run an AFL++ fuzzing campaign for a C/C++ target. Compiles with LTO, LLVM, or GCC instrumentation, runs single or multi-core campaigns, triages crashes, and minimizes the corpus. Requests explicit approval before system-level changes. Not for libFuzzer harness campaigns — use libfuzzer.'

AFL++

Contract

| Field | Bound contract | |---|---| | Trigger | User needs AFL++ setup, multi-core campaign operation, corpus handling, or AFL++ crash triage for a C/C++ target. | | Authority | Reversible local execution of AFL++ binaries and writes to build artifacts, corpora, logs, and output directories under the working directory. The skill must request explicit user approval before any system-level change: package installation, Docker image pull, kernel tuning, GRUB mutation, or reboot. Roll back by killing campaign processes and removing the output and state directories. | | Side effect | Local writes to compiled artifacts, seed and output corpora, log files, and short-lived Docker containers. No mutation of source under test beyond compilation. | | Done | AFL++ target runs against a seed corpus with the intended instrumentation and produces interpretable campaign output. |

Inputs

  • Target source tree (C/C++) with a fuzz harness or a program that reads stdin, files, or argv. Required.
  • Seed corpus directory with at least one non-empty file. Required; created if absent.
  • Run location: host or Docker. Required; default Docker.
  • Compilation mode preference (LTO, LLVM, or GCC). Optional; default tries LTO, falls back to LLVM.
  • Core count for multi-core campaigns. Optional; default is a single instance.
  • Dictionary file for format-aware fuzzing. Optional.
  • Sanitizer selection (ASan, UBSan, none). Optional; default none.

Procedure

1. Select run location and verify AFL++

Choose host or Docker. For Docker, pull the image with explicit user approval:

docker pull aflplusplus/aflplusplus:stable

For host installation, request approval before running:

apt install afl++ lld-<clang-version>

Verify the binary: afl-fuzz --version (host) or docker run --rm aflplusplus/aflplusplus:stable afl-fuzz --version (Docker).

Kernel tuning (afl-system-config) gives up to 15% more executions per second but disables OS security features. Request explicit approval before running it, and only on a dedicated VM. For maximum performance, afl-persistent-config plus update-grub and reboot are required — these are irreversible system changes that need approval. Do not run kernel tuning on production or development systems.

Done when: AFL++ is verified and any system-level changes were approved by the user.

2. Compile the target with instrumentation and sanitizers

Write or locate a fuzz harness. AFL++ supports libFuzzer-style harnesses:

#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < MIN_SIZE || size > MAX_SIZE) return 0;
    target_function(data, size);
    return 0;
}

Reset global state between runs, keep the harness deterministic, free allocated memory, and return 0. For programs reading stdin or files, no harness is needed — compile the program directly and fuzz via stdin or the @@ file placeholder.

Compile with the chosen instrumentation mode. Try LTO first for best performance; fall back to LLVM mode if LTO fails to link; use the GCC plugin only when the project requires GCC:

# LTO mode (preferred)
afl-clang-lto++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
# LLVM mode (fallback)
afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
# GCC plugin
afl-g++-fast -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz

-DNO_MAIN=1 skips the main function when using a libFuzzer harness. For static libraries and object files, use -fsanitize=fuzzer-no-link to instrument without linking the fuzzer runtime. The GCC version must match the version used to compile the AFL++ GCC plugin.

Add sanitizers if requested. AddressSanitizer (AFL_USE_ASAN=1) and UBSan (AFL_USE_UBSAN=1) find memory corruption and undefined behavior that do not crash immediately:

AFL_USE_ASAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer harness.cc main.cc -o fuzz
AFL_USE_UBSAN=1 afl-clang-fast++ -DNO_MAIN=1 -O2 -fsanitize=fuzzer,undefined harness.cc main.cc -o fuzz

The -m memory limit flag is not supported with ASan because ASan reserves 20 TB of virtual memory. In multi-core setups, run only one ASan job per 4–8 non-ASan jobs.

Done when: the target compiles and links with the chosen instrumentation and sanitizer flags.

3. Configure environment and run the campaign

Create a seed corpus with at least one non-empty file:

mkdir seeds && echo "aaaa" > seeds/minimal_seed

For real projects, gather representative inputs from example files, the project test suite, or minimal valid inputs for the target format.

Set the environment variables that matter for the campaign:

  • AFL_TMPDIR=/dev/shm: always set; uses tmpfs to improve performance and avoid SSD wear.
  • AFL_FAST_CAL=1: for slow targets (>10 ms/exec); speeds calibration ~2.5x with negligible precision loss.
  • AFL_TESTCACHE_SIZE=100: on all instances; caches test cases in memory (default 50 MB; 50–250 MB works well).
  • AFL_FINAL_SYNC=1: on the primary -M instance only; needed for later afl-cmin, not for fuzzing itself.
  • AFL_EXIT_ON_TIME=3600 or AFL_EXIT_WHEN_DONE=1: for CI or automated fuzzing to bound runtime.
  • AFL_NO_UI=1: for headless environments.

Run a single-core campaign:

AFL_TMPDIR=/dev/shm afl-fuzz -i seeds -o out -- ./fuzz

Useful flags: -G 4000 (max input length), -t 1000 (per-case timeout ms), -m 1000 (memory limit MB, not with ASan), -x ./dict.dict (dictionary).

For multi-core campaigns, start a primary instance and one secondary per available core, all sharing the same -o state directory:

AFL_TMPDIR=/dev/shm AFL_FINAL_SYNC=1 AFL_TESTCACHE_SIZE=100 afl-fuzz -M primary -i seeds -o state -- ./fuzz 1>primary.log 2>primary.error </dev/null &
AFL_TMPDIR=/dev/shm AFL_TESTCACHE_SIZE=100 afl-fuzz -S secondary01 -i seeds -o state -- ./fuzz 1>secondary01.log 2>secondary01.error </dev/null &

The </dev/null redirect is required. A backgrounded process that reads the terminal receives SIGTTIN and stops without it. List running jobs with jobs; stop all with kill $(jobs -p).

To enable CMPLOG/RedQueen constraint solving, build with AFL_LLVM_CMPLOG=1 and run one secondary with -c0:

AFL_LLVM_CMPLOG=1 afl-fuzz -c0 -S cmplog -i seeds -o state -- ./fuzz 1>cmplog.log 2>cmplog.error </dev/null &

Done when: the campaign is running with the configured environment and core count.

4. Monitor and triage crashes

Without a TTY, afl-fuzz writes plain status lines to the log and to state/<instance>/fuzzer_stats. Check status:

afl-whatsup state/

Read these fields: execs/sec (speed, higher is better), cycles done (queue passes completed), corpus count (unique test cases in queue), saved crashes (unique crashes found), stability (should be near 100%; below 85% indicates non-deterministic behavior). For coverage plots: afl-plot state/default out_graph/ (requires gnuplot on host; the Docker image ships gnuplot-nox).

Triage crashes by re-executing each file in state/default/crashes/ (or out/default/crashes/) against the target:

./fuzz state/default/crashes/id:000000,sig:06,src:000002,time:286,execs:13105,op:havoc,rep:4

The crash filename encodes the signal (sig:06), source, time, execs, operation, and rep. Hangs are in state/default/hangs/.

Done when: campaign status is read and each crash file is re-executed against the target with its signal and reproduction command recorded.

5. Minimize corpus and stop

Minimize the corpus after a campaign to keep only unique coverage:

afl-cmin -i state/default/queue -o minimized_corpus -- ./fuzz

Stop all campaign processes and clean up: kill $(jobs -p), then remove out/ or state/ and log files to roll back. The target source is unchanged except for compiled artifacts.

Done when: the corpus is minimized and all campaign processes are stopped.

Failure and recovery

  • Build failure (LTO link error): fall back to LLVM mode (afl-clang-fast); if the project requires GCC, use afl-gcc-fast or afl-g++-fast. Do not proceed with an uninstrumented binary — it produces no coverage data.
  • GCC plugin version mismatch: ensure the system GCC matches the AFL++ plugin build; install gcc-<version>-plugin-dev. Do not patch around the mismatch.
  • Low stability (<85%): the target is non-deterministic. Switch from a LLVMFuzzerTestOneInput harness to stdin or file-input fuzzing, or fix the non-determinism in the target. Do not report done with low stability.
  • Low execs/sec (<1k): switch to a persistent-mode (LLVMFuzzerTestOneInput) harness for 10–20x speedup, or set AFL_TMPDIR=/dev/shm.
  • No crashes found: recompile with AFL_USE_ASAN=1 or AFL_USE_UBSAN=1; memory corruption often does not crash without a sanitizer.
  • Memory limit exceeded with ASan: remove the -m flag; ASan reserves 20 TB virtual memory and is incompatible with -m.
  • Backgrounded job shows "Stopped": the </dev/null redirect is missing; restart the job with it to avoid SIGTTIN.
  • Docker "input device is not a TTY": omit -t for non-interactive runs. For the interactive UI, run in host mode in a terminal.
  • Partial result rule: if the campaign stops early, the state/ or out/ directory still contains valid queue, crashes, and fuzzer_stats; report what was captured rather than discarding it.
  • Rollback: kill all fuzzers (kill $(jobs -p)), remove out/, state/, log files, and the compiled fuzz binary. Source under test is unchanged beyond compilation. Do not attempt to reverse afl-system-config or afl-persistent-config on a shared system — reboot the dedicated VM instead.

Output

  • A compiled, instrumented fuzz target binary.
  • A campaign output directory (out/ or state/) containing queue/ (test cases), crashes/ (crash-reproducing inputs), hangs/, fuzzer_stats (campaign statistics), and plot_data (coverage time series).
  • A status report covering execs/sec, cycles done, corpus count, saved crashes, and stability.
  • A crash triage result: each crash file re-executed against the target with its signal and reproduction command recorded.
  • A minimized corpus directory when afl-cmin was run.

Related Skills

View on GitHub
GitHub Stars0
CategoryDevelopment
UpdatedNaNy ago
Forks0

Security Score

68/100

Audited on Invalid Date

2 medium1 low