Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Integrate AFL++ coverage-guided fuzz testing into CI/CD pipelines to discover memory corruption, input handling, and logic vulnerabilities in C/C++ and compiled applications.
.claude/skills/implementing-fuzz-testing-in-cicd-with-aflplusplus/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✗ | = Same ✗ | — | — |
AFL++ (American Fuzzy Lop Plus Plus) is a community-maintained fork of AFL that provides state-of-the-art coverage-guided fuzz testing for discovering vulnerabilities in compiled applications. AFL++ uses genetic algorithms to mutate inputs, tracking code coverage to find new execution paths that trigger crashes, hangs, and undefined behavior. In CI/CD environments, AFL++ can be integrated to continuously test parsers, protocol handlers, file format processors, and any code that handles untrusted input. AFL++ supports persistent mode for high-speed fuzzing (up to 100,000+ executions per second), custom mutators, QEMU mode for binary-only fuzzing, and CmpLog/RedQueen for automatic dictionary extraction.
apt install aflplusplus or built from source)AFL++ instruments the target binary at compile time (or via QEMU/Frida for binary-only targets) to track which code paths each input exercises. When a mutated input triggers a new code path, it is saved to the corpus for further mutation. This feedback loop enables AFL++ to systematically explore program state space.
| Mode | Use Case | Performance | |------|----------|-------------| | afl-clang-fast (LTO) | Source available, best performance | Highest | | afl-clang-fast | Source available, standard | High | | afl-gcc-fast | GCC-based projects | High | | QEMU mode | Binary-only, no source | Medium | | Frida mode | Binary-only, cross-platform | Medium | | Unicorn mode | Firmware, embedded | Low |
Persistent mode avoids fork overhead by fuzzing within a loop:
c#include <unistd.h> __AFL_FUZZ_INIT(); int main() { __AFL_INIT(); unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { int len = __AFL_FUZZ_TESTCASE_LEN; // Process buf[0..len-1] parse_input(buf, len); } return 0; }
Create a harness that feeds AFL++ input to the target function:
c// fuzz_harness.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include "target_parser.h" __AFL_FUZZ_INIT(); int main() { __AFL_INIT(); unsigned char *buf = __AFL_FUZZ_TESTCASE_BUF; while (__AFL_LOOP(10000)) { int len = __AFL_FUZZ_TESTCASE_LEN; if (len < 4) continue; // Reset state between iterations parser_context_t ctx; parser_init(&ctx); parser_process(&ctx, buf, len); parser_cleanup(&ctx); } return 0; }
bash# Standard instrumentation export CC=afl-clang-fast export CXX=afl-clang-fast++ # Enable AddressSanitizer for better crash detection export AFL_USE_ASAN=1 # Build the target with instrumentation $CC -o fuzz_harness fuzz_harness.c -ltarget_parser -fsanitize=address # Build a CmpLog binary for better coverage $CC -o fuzz_harness_cmplog fuzz_harness.c -ltarget_parser \ -fsanitize=address -DCMPLOG
bashmkdir -p corpus/ # Add valid input samples cp test_inputs/* corpus/ # Minimize the corpus afl-cmin -i corpus/ -o corpus_min/ -- ./fuzz_harness @@ # Further minimize individual inputs mkdir -p corpus_tmin/ for f in corpus_min/*; do afl-tmin -i "$f" -o "corpus_tmin/$(basename $f)" -- ./fuzz_harness @@ done
GitHub Actions:
yamlname: Fuzz Testing on: push: branches: [main] schedule: - cron: '0 2 * * *' # Nightly fuzzing jobs: fuzz: runs-on: ubuntu-latest timeout-minutes: 120 steps: - uses: actions/checkout@v4 - name: Install AFL++ run: | sudo apt-get update sudo apt-get install -y aflplusplus - name: Restore corpus cache uses: actions/cache@v4 with: path: corpus/ key: fuzz-corpus-${{ github.sha }} restore-keys: fuzz-corpus- - name: Build fuzzing harness run: | export CC=afl-clang-fast export AFL_USE_ASAN=1 make fuzz_harness - name: Run AFL++ fuzzing (CI mode) env: AFL_CMPLOG_ONLY_NEW: 1 AFL_FAST_CAL: 1 AFL_NO_STARTUP_CALIBRATION: 1 run: | mkdir -p findings/ timeout 7200 afl-fuzz \ -S ci_fuzzer \ -i corpus/ \ -o findings/ \ -t 5000 \ -- ./fuzz_harness @@ || true - name: Check for crashes run: | CRASHES=$(find findings/ -path "*/crashes/*" -not -name "README.txt" | wc -l) echo "Found $CRASHES unique crashes" if [ "$CRASHES" -gt 0 ]; then echo "::error::AFL++ found $CRASHES crashes" for crash in findings/*/crashes/*; do [ -f "$crash" ] && echo "Crash: $crash ($(wc -c < $crash) bytes)" done exit 1 fi - name: Update corpus cache if: always() run: | afl-cmin -i findings/ci_fuzzer/queue/ -o corpus/ -- ./fuzz_harness @@
bash# Launch multiple secondary instances for better coverage for i in $(seq 1 $(nproc)); do afl-fuzz -S fuzzer_$i \ -i corpus/ \ -o findings/ \ -- ./fuzz_harness @@ & done # Wait for all fuzzers wait # Merge and minimize corpus afl-cmin -i findings/*/queue/ -o corpus_merged/ -- ./fuzz_harness @@
bash# Reproduce and categorize crashes for crash in findings/*/crashes/*; do echo "=== Testing: $crash ===" timeout 5 ./fuzz_harness_asan "$crash" 2>&1 | head -20 echo "---" done # Deduplicate crashes by stack trace afl-collect findings/ crashes_deduped/ -- ./fuzz_harness @@
| Setting | CI Short Run | Nightly Long Run | |---------|-------------|-----------------| | Duration | 30-60 min | 4-24 hours | | Mode | -S (secondary only) | -S (no -M for CI) | | AFL_CMPLOG_ONLY_NEW | 1 | 1 | | AFL_FAST_CAL | 1 | 0 | | AFL_NO_STARTUP_CALIBRATION | 1 | 0 | | Corpus caching | Required | Required | | Parallel instances | 1-2 | nproc |
bash# View fuzzing statistics afl-whatsup findings/ # Key metrics to track: # - Total paths found (code coverage indicator) # - Unique crashes / unique hangs # - Stability percentage (should be >90%) # - Exec speed (execs/sec) # - Cycles done (full corpus cycles completed)
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-17 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +18 percentage points is the difference between those two pass rates over the 21 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.