Testland
Browse all skills & agents

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.

Install with skills.sh (any agent)

npx skills add testland/qa --skill libfuzzer-cpp
View source

libfuzzer-cpp

Overview

This skill wraps LLVM's libFuzzer (per llvm.org/docs/LibFuzzer.html (opens in new window)) for C/C++ targets. Composes with:

  • sanitiser-integration-reference for ASan + UBSan integration
  • corpus-management-reference for seed / evolved-corpus / crash artefact discipline

When to use

  • Fuzzing a C / C++ library function (parser, decoder, validator).
  • Targeting a specific function - in-process fuzzing is faster than out-of-process AFL.
  • Pairing with ASan + UBSan for memory-safety + UB detection.

Authoring

The fuzz target

Define the entry point LLVMFuzzerTestOneInput:

#include <cstddef>
#include <cstdint>
#include "your_library.h"

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    your_parser(Data, Size);
    return 0;
}

Per LLVM docs, the function signature is fixed: takes a const byte buffer + size, returns int (must return 0 for normal execution; non-zero values are reserved).

The fuzzer calls this function repeatedly with mutated Data. The target's job is to drive the library code under test and let sanitisers + asserts catch bugs.

Initialisation

Optional one-time setup:

extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
    your_library_init();
    return 0;
}

Build

Standard build flag:

clang -g -O1 \
  -fsanitize=fuzzer,address,undefined \
  -fno-sanitize-recover=all \
  -fno-omit-frame-pointer \
  fuzz_target.cc your_library.cc -o fuzz_target

Per sanitiser-integration-reference: ASan + UBSan is the default pair; add MSan in a separate binary if needed.

Tips for an effective target

TipWhy
Keep the target smallFaster iteration; clearer coverage
Avoid global state between runsCross-input contamination defeats coverage guidance
Use the full inputDon't if (Size < 100) return 0; unless the lib requires
Avoid expensive I/O / network in the targetSlows iterations
Use FuzzedDataProvider for structured inputsSplits Data into typed sub-values

FuzzedDataProvider (from LLVM's compiler-rt/include/fuzzer/FuzzedDataProvider.h):

#include <fuzzer/FuzzedDataProvider.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    FuzzedDataProvider fdp(Data, Size);
    int port = fdp.ConsumeIntegralInRange(1, 65535);
    std::string host = fdp.ConsumeRandomLengthString(64);
    std::vector<uint8_t> body = fdp.ConsumeRemainingBytes<uint8_t>();
    your_request_handler(host, port, body);
    return 0;
}

Running

Basic run

mkdir corpus/ seeds/
# Populate seeds/ with hand-curated inputs
./fuzz_target -max_total_time=3600 corpus/ seeds/

The first directory is writable (evolved corpus); subsequent are read-only seeds (per corpus-management-reference).

Common flags

Most-used: -max_total_time=N, -runs=N (-1 = infinite), -dict=path, -fork=N, -workers=N, -merge=1 (corpus minimisation), -rss_limit_mb=N (default 2048), -timeout=N (per-input, default 1200). Full table (per llvm.org/docs/LibFuzzer.html (opens in new window)): references/flags-and-ci.md.

Parallel fuzzing

./fuzz_target -fork=8 -max_total_time=3600 corpus/ seeds/

-fork=N spawns N processes, each with its own corpus subset. Combine corpora periodically with -merge=1.

Reproducing a crash

./fuzz_target crash-<sha1>
# Sanitiser report prints to stderr; same as the original crash

Verify: confirm the replay prints the same sanitiser bug class and top stack frame as the original finding before minimising. If it does not reproduce, the artefact is stale against the current build (target or library rebuilt) - rebuild the target and re-run before proceeding.

Minimise the crash input:

./fuzz_target -minimize_crash=1 -runs=10000 crash-<sha1>
# Writes minimized-from-crash-<sha1> with the smallest reproducer

Dictionary file

For structured formats:

# fuzz.dict
"{"
"}"
"["
"]"
"true"
"false"
"null"
"\":\""

Invoke: ./fuzz_target -dict=fuzz.dict corpus/.

Parsing results

libFuzzer crash artefacts are saved as:

  • crash-<sha1> - segfault / sanitiser-detected error
  • leak-<sha1> - memory leak detected by LSan
  • timeout-<sha1> - exceeded -timeout
  • oom-<sha1> - RSS exceeded -rss_limit_mb

Each file's contents are the input bytes that triggered the crash. Pair with the sanitiser report (stderr) for stack + allocation site.

For automated parsing (e.g., file as a bug), feed the sanitiser-report output to bug-report-from-failure (in the qa-defect-management plugin):

./fuzz_target crash-<sha1> 2> sanitiser-report.txt
python scripts/file-bug-from-asan.py sanitiser-report.txt crash-<sha1>

CI integration

Short smoke fuzz (5 min) on every PR: build with -fsanitize=fuzzer,address,undefined, cache fuzz/corpus, run ./fuzz_target -max_total_time=300 fuzz/corpus fuzz/seeds, and upload crash-* / leak-* / timeout-* / oom-* artifacts. Full workflow: references/flags-and-ci.md.

For long-running campaigns, ossfuzz-integration is the canonical infrastructure.

Anti-patterns

Anti-patternWhy it failsFix
LLVMFuzzerTestOneInput with global state mutationCross-input contamination breaks coverage signalReset state per call or use LLVMFuzzerInitialize
Fuzz target without ASan + UBSanCatches only crashes; 80%+ of bugs missedAlways compose with sanitisers
No corpus minimisation everCorpus grows unbounded; cycle time degradesWeekly -merge=1
Crash committed without minimisationLarge bug-report attachmentsAlways -minimize_crash=1
Single huge fuzz targetSlow iterations; coverage attribution opaqueSplit into multiple targets per function
Ignoring -rss_limit_mb OOMsFalse crash classSet limit explicit; or disable allocator-related target paths
No dictionary for structured formatsFuzzer slowly rediscovers grammarAlways supply -dict= for JSON / XML / SQL / proto

Limitations

  • In-process only. Doesn't fuzz inter-process boundaries; for network protocols see AFL++ in -Q (QEMU) mode or specialised tools.
  • C / C++ + Rust + Swift only. Other languages have their own fuzzers (Atheris, Jazzer, Go native).
  • Coverage instrumentation overhead. Hot inner loops slow significantly under -fsanitize=fuzzer.
  • Crash uniqueness heuristic. libFuzzer dedup is sha1-based on the input; the same bug from two inputs creates two artefacts - pair with crash-stack-deduplication tooling.
  • No coverage report by default. Use -coverage flag or external llvm-profdata + llvm-cov for line-level coverage.

References

  • LLVM libFuzzer - llvm.org/docs/LibFuzzer.html (opens in new window).
  • FuzzedDataProvider.h - github.com/llvm/llvm-project/blob/main/compiler-rt/include/fuzzer/FuzzedDataProvider.h.
  • Composes: sanitiser-integration-reference, corpus-management-reference.
  • Sibling fuzzers: afl-plus-plus (out-of-process, multi-language), cargo-fuzz-rust (Rust wrapper around libFuzzer), atheris-python-fuzzing, jazzer-jvm-fuzzing, go-native-fuzzing, ossfuzz-integration.
  • Dispatcher: fuzz-tool-selector.

libFuzzer: runtime flags and CI

View source (opens in new window)

libFuzzer: runtime flags and CI

Deep reference for libfuzzer-cpp. The core harness / build / run / reproduce workflow lives in the skill spine; this file holds the full runtime-flag table and the CI job.

Common flags

Per llvm.org/docs/LibFuzzer.html (opens in new window):

FlagEffect
-max_total_time=NStop after N seconds
-runs=NStop after N executions (-1 = infinite)
-dict=pathUse dictionary file
-seed=NRandom seed
-fork=NRun N parallel fork-mode workers
-workers=NNumber of parallel worker processes
-jobs=NTotal number of jobs to run across workers
-merge=1Corpus minimisation mode
-print_final_stats=1Print stats summary on exit
-rss_limit_mb=NRSS memory limit (default 2048)
-timeout=NPer-input timeout in seconds (default 1200)
-only_ascii=1Restrict to ASCII bytes

CI integration

Short smoke fuzz on every PR:

jobs:
  fuzz:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - name: Install clang
        run: sudo apt-get install -y clang lld
      - name: Build fuzz target
        run: |
          clang++ -g -O1 \
            -fsanitize=fuzzer,address,undefined \
            -fno-sanitize-recover=all \
            -fno-omit-frame-pointer \
            fuzz/fuzz_target.cc lib/parser.cc -o fuzz_target
      - uses: actions/cache@v4
        with:
          path: fuzz/corpus
          key: fuzz-corpus-${{ github.sha }}
          restore-keys: fuzz-corpus-
      - name: Smoke fuzz (5 min)
        run: ./fuzz_target -max_total_time=300 fuzz/corpus fuzz/seeds
      - name: Upload crashes
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: crashes
          path: |
            crash-*
            leak-*
            timeout-*
            oom-*

Related skills

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.

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.

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.