← Back to posts
comparison guide self-hosted · · 12 min read

AFL++ vs Honggfuzz vs libFuzzer: Self-Hosted Fuzzing Guide 2026

Compare AFL++, Honggfuzz, and libFuzzer — the top open-source fuzzing tools for self-hosted security testing. Docker setup, CI/CD integration, and practical examples.

OS
Editorial Team

Fuzzing is one of the most effective techniques for discovering security vulnerabilities, memory corruption bugs, and unexpected edge cases in software. Instead of writing targeted test cases, fuzzers automatically generate thousands (or millions) of random or mutated inputs and feed them to your program, watching for crashes, hangs, or sanitizer violations.

For teams running self-hosted infrastructure, having a dedicated fuzzing pipeline on your own hardware means you can test proprietary code, process sensitive test corpora, and scale without paying for cloud compute. In this guide, we compare three of the most widely used open-source fuzzing tools: AFL++, Honggfuzz, and libFuzzer.

FeatureAFL++HonggfuzzlibFuzzer
GitHub Stars6,4793,330Part of LLVM (38,034)
Primary LanguageCCC++
Fuzzing ModeFork serverIn-process / forkIn-process
Coverage GuidanceYes (compile-time, QEMU, Unicorn)Yes (compile-time, HW PT)Yes (SanitizerCoverage)
Parallel FuzzingBuilt-in (afl-fuzz -M/-S)Built-in (–cores)External (via orchestrator)
Corpus Minimizationafl-cmin, afl-tminBuilt-inllvm-cov / custom
Dictionary SupportYes (-x)Yes (–dictionary)Yes (-dict)
Sanitizer SupportASan, LSan, UBSan, MSanASan, UBSan, HWASANFull (ASan, MSan, UBSan, TSan)
Language SupportC/C++ (native), others via QEMU/UnicornC/C++ (native), Java via JNIC/C++ (LLVM-based), Rust
LicenseApache 2.0Apache 2.0Apache 2.0 with LLVM exceptions
Last Updated2026-04-162026-04-132026-04-25
Learning CurveModerateLowModerate-High

Why Run a Self-Hosted Fuzzing Pipeline?

Cloud-based fuzzing services like OSS-Fuzz and Fuzzit offer convenience but come with trade-offs. Self-hosting your fuzzing infrastructure gives you:

  • Full control over test corpora — sensitive or proprietary inputs never leave your network
  • Unlimited scale on your own hardware — no per-core-hour billing, run 24/7 on bare metal
  • Custom instrumentation — instrument any binary, including closed-source software via QEMU mode
  • Persistent state — corpora, crash data, and coverage maps survive across CI/CD runs
  • Integration with existing CI/CD — run fuzzing as a nightly job in your self-hosted GitLab, Gitea, or Jenkins pipeline

For organizations handling security-sensitive software — cryptography libraries, network protocol parsers, file format handlers — running fuzzing on self-hosted infrastructure is not just convenient, it’s often a compliance requirement.

AFL++: The Community-Forked Powerhouse

AFL++ is the most actively maintained fork of the original American Fuzzy Lop (AFL) by Michal Zalewski. It combines hundreds of community patches into a single, cohesive distribution and has become the de facto standard for coverage-guided fuzzing.

Key Features

  • Multiple instrumentation modes: Compile-time (GCC/Clang plugins), QEMU (black-box binary instrumentation), and Unicorn (emulation-based for unusual architectures)
  • Power schedules: MOpt, exploit, rare, and seek strategies that automatically tune mutation rates based on coverage feedback
  • Collaborative fuzzing: Run multiple instances with -M (main) and -S (secondary) modes to share discovered paths
  • LAF-Intel and Redqueen: Automatic comparison splitting that breaks through magic byte checks and string comparisons

Installation on Ubuntu/Debian

1
2
3
4
5
6
7
8
sudo apt update
sudo apt install -y build-essential python3-dev python3-setuptools \
    git libtool-bin libglib2.0-dev libpixman-1-dev clang llvm

git clone https://github.com/AFLplusplus/AFLplusplus.git
cd AFLplusplus
make distrib
sudo make install

Docker Setup for AFL++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# docker-compose.yml — AFL++ fuzzing environment
version: "3.8"

services:
  afl-fuzz:
    build:
      context: .
      dockerfile: Dockerfile.afl
    volumes:
      - ./test_corpus:/afl/corpus
      - ./output:/afl/output
      - ./target_binary:/afl/target:ro
    command: >
      afl-fuzz -i /afl/corpus -o /afl/output
      -- /afl/target @@
    deploy:
      resources:
        limits:
          cpus: "4"
          memory: 4G
    restart: unless-stopped
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Dockerfile.afl
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential clang llvm git libtool-bin python3-dev \
    && rm -rf /var/lib/apt/lists/*

RUN git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus.git /opt/afl \
    && cd /opt/afl && make distrib && make install

WORKDIR /afl
COPY target_binary /afl/target
RUN chmod +x /afl/target

CMD ["afl-fuzz", "-i", "/afl/corpus", "-o", "/afl/output", "--", "/afl/target", "@@"]

Running a Fuzzing Session

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Compile the target with AFL's Clang instrumentation
export AFL_USE_ASAN=1
afl-clang-fast -o target_binary target.c

# Create initial corpus directory with seed inputs
mkdir corpus
echo "seed1" > corpus/seed1.txt
echo "seed2" > corpus/seed2.txt

# Start the main fuzzer instance
afl-fuzz -i corpus -o findings -M main -- ./target_binary @@

# In another terminal, start secondary instances
afl-fuzz -i corpus -o findings -S worker1 -- ./target_binary @@
afl-fuzz -i corpus -o findings -S worker2 -- ./target_binary @@

Honggfuzz: The Swiss Army Knife of Fuzzing

Honggfuzz, originally developed by Google’s security team, is a versatile fuzzer that supports both evolutionary (feedback-driven) and black-box fuzzing modes. Its standout feature is built-in support for hardware-assisted fuzzing via Intel PT (Processor Trace), which provides near-zero-overhead coverage feedback.

Key Features

  • Hardware-assisted coverage: Intel PT mode delivers coverage data with minimal performance overhead compared to compile-time instrumentation
  • Multiple fuzzing modes: In-process (library fuzzing), external process (fork-based), and persistent mode
  • Built-in sanitizers: Supports AddressSanitizer, UndefinedBehaviorSanitizer, and Hardware-assisted ASan (HWA San)
  • Simple CLI: Single binary with intuitive flags, no complex setup required
  • Cross-platform: Runs on Linux, macOS, and Windows (WSL)

Installation on Ubuntu/Debian

1
2
3
4
5
6
7
8
9
sudo apt update
sudo apt install -y build-essential binutils-dev libunwind-dev \
    libblocksruntime-dev liblzma-dev libiberty-dev libbfd-dev \
    clang llvm

git clone https://github.com/google/honggfuzz.git
cd honggfuzz
make
sudo make install

Docker Setup for Honggfuzz

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# docker-compose.yml — Honggfuzz environment
version: "3.8"

services:
  honggfuzz:
    build:
      context: .
      dockerfile: Dockerfile.honggfuzz
    volumes:
      - ./test_corpus:/hfuzz/input
      - ./output:/hfuzz/output
      - ./target_binary:/hfuzz/target:ro
    command: >
      honggfuzz --input /hfuzz/input --output /hfuzz/output
      --persistent --timeout 5 --threads 4
      -- /hfuzz/target ___FILE___
    security_opt:
      - seccomp=unconfined
    deploy:
      resources:
        limits:
          cpus: "4"
          memory: 4G
    restart: unless-stopped
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Dockerfile.honggfuzz
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential clang llvm binutils-dev libunwind-dev \
    libblocksruntime-dev liblzma-dev libiberty-dev \
    && rm -rf /var/lib/apt/lists/*

RUN git clone --depth 1 https://github.com/google/honggfuzz.git /opt/honggfuzz \
    && cd /opt/honggfuzz && make && make install

WORKDIR /hfuzz
COPY target_binary /hfuzz/target
RUN chmod +x /hfuzz/target

CMD ["honggfuzz", "--input", "/hfuzz/input", "--output", "/hfuzz/output", \
     "--persistent", "--timeout", "5", "--threads", "4", "--", \
     "/hfuzz/target", "___FILE___"]

Running a Fuzzing Session

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Compile the target with Honggfuzz's compiler wrapper
HFUZZ_CC_ARGS="-fsanitize=address,undefined" hfuzz-clang -o target_binary target.c

# Create seed corpus
mkdir input_corpus
echo "test seed data" > input_corpus/seed1.bin
dd if=/dev/urandom of=input_corpus/seed2.bin bs=64 count=1

# Run with 4 parallel threads
honggfuzz --input input_corpus --output crashes \
    --persistent --timeout 5 --threads 4 \
    -- ./target_binary ___FILE___

# Or use Intel PT mode for hardware-assisted coverage (requires root + Intel CPU)
sudo honggfuzz --input input_corpus --output crashes \
    --linux_perf_instr --threads 4 \
    -- ./target_binary ___FILE___

libFuzzer: LLVM’s In-Process Fuzzing Engine

libFuzzer is an in-process, coverage-guided fuzzing engine that is tightly integrated with LLVM’s SanitizerCoverage and the Clang compiler. Unlike AFL++ and Honggfuzz, which spawn separate processes for each test input, libFuzzer runs inside the target process itself, making it exceptionally fast for fuzzing libraries and parsers.

Key Features

  • In-process execution: No fork overhead — millions of executions per second
  • Deep LLVM integration: Uses SanitizerCoverage for fine-grained coverage feedback
  • First-class sanitizer support: Works seamlessly with ASan, MSan, UBSan, and TSan
  • Rust support: Native integration via the cargo-fuzz tool
  • Value profiling: Tracks which comparison values lead to new coverage

Installation (via LLVM/Clang)

libFuzzer ships with LLVM 6.0+ and Clang. On most systems, installing Clang is sufficient:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Ubuntu/Debian
sudo apt install -y clang llvm

# Or build from source for the latest version
git clone --depth 1 https://github.com/llvm/llvm-project.git
cd llvm-project
mkdir build && cd build
cmake -G "Ninja" -DLLVM_ENABLE_PROJECTS="clang" \
    -DCMAKE_BUILD_TYPE=Release ../llvm
ninja

Docker Setup for libFuzzer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# docker-compose.yml — libFuzzer environment
version: "3.8"

services:
  libfuzzer:
    build:
      context: .
      dockerfile: Dockerfile.libfuzzer
    volumes:
      - ./corpus:/fuzz/corpus
      - ./output:/fuzz/output
    command: >
      bash -c "cd /fuzz && ./fuzz_target -artifact_prefix=/fuzz/output/
      -max_total_time=3600 /fuzz/corpus"
    deploy:
      resources:
        limits:
          cpus: "2"
          memory: 2G
    restart: unless-stopped
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# Dockerfile.libfuzzer
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y --no-install-recommends \
    clang llvm build-essential cmake ninja-build \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /fuzz

# Compile the fuzz target with libFuzzer
COPY fuzz_target.cpp /fuzz/
RUN clang++ -fsanitize=fuzzer,address -O1 \
    -o /fuzz/fuzz_target /fuzz/fuzz_target.cpp

CMD ["bash", "-c", \
     "./fuzz_target -artifact_prefix=/fuzz/output/ \
      -max_total_time=3600 /fuzz/corpus"]

Writing a libFuzzer Target

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// fuzz_target.cpp — Example libFuzzer target for a parsing function
#include <cstdint>
#include <cstddef>

// The function you want to fuzz
extern int ParseInput(const uint8_t* data, size_t size);

// libFuzzer entry point — the fuzzer calls this with random data
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
    if (size == 0) return 0;
    ParseInput(data, size);
    return 0;  // Non-zero return indicates a "bug" to libFuzzer
}
1
2
3
4
5
6
# Compile with libFuzzer and AddressSanitizer
clang++ -fsanitize=fuzzer,address -O1 -g -o fuzz_target fuzz_target.cpp target_lib.o

# Run the fuzzer
mkdir corpus
./fuzz_target -max_total_time=600 corpus/

Deep Comparison: Choosing the Right Fuzzer

CriteriaAFL++HonggfuzzlibFuzzer
Best forGeneral-purpose binary fuzzingQuick setup, hardware-assisted coverageLibrary/parser fuzzing, Rust projects
SpeedHigh (fork server minimizes overhead)Very high (in-process mode)Highest (no fork overhead)
Black-box supportExcellent (QEMU mode)Good (external process mode)Limited (requires source)
CI/CD integrationGood (headless mode)Good (non-interactive flags)Excellent (single binary execution)
Crash deduplicationBuilt-in (afl-uniquify)Built-in (unique crash sorting)Manual (requires external tools)
Persistent modeYes (__AFL_LOOP)Yes (--persistent)Native (in-process by default)
Fuzzing distributed targetsYes (QEMU for any binary)Partial (needs recompilation)No (requires LLVM instrumentation)

When to Choose AFL++

Pick AFL++ when you need the most battle-tested fuzzer with the widest range of instrumentation options. Its QEMU mode can fuzz any binary without source code, making it ideal for testing third-party or legacy software. The collaborative fuzzing model (-M/-S) scales well across multiple cores or machines.

When to Choose Honggfuzz

Honggfuzz is the best choice when you want a simple, single-binary setup that just works. Its hardware-assisted mode (Intel PT) provides excellent coverage with near-zero overhead — something compile-time instrumentation cannot match. The straightforward CLI makes it ideal for quick security audits.

When to Choose libFuzzer

libFuzzer excels at in-process fuzzing of libraries, parsers, and data processing functions. If your codebase already uses Clang/LLVM, libFuzzer integrates seamlessly. It’s the top choice for Rust projects (via cargo-fuzz) and for running in CI/CD pipelines where fast execution matters.

Integrating Fuzzing into Self-Hosted CI/CD

A practical fuzzing pipeline runs continuously on your self-hosted infrastructure. Here’s a GitLab CI example that runs nightly fuzzing with AFL++:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# .gitlab-ci.yml — Nightly fuzzing pipeline
stages:
  - fuzz

variables:
  FUZZ_TIME: 3600  # 1 hour per job

nightly-afl-fuzz:
  stage: fuzz
  image: ubuntu:24.04
  script:
    - apt-get update && apt-get install -y build-essential clang llvm git
    - git clone --depth 1 https://github.com/AFLplusplus/AFLplusplus.git
    - cd AFLplusplus && make distrib && make install && cd ..
    - export AFL_USE_ASAN=1
    - afl-clang-fast -o fuzz_target src/parser.c
    - mkdir -p corpus && echo "seed" > corpus/seed.txt
    - timeout $FUZZ_TIME afl-fuzz -i corpus -o findings -M main -- ./fuzz_target @@
  artifacts:
    when: always
    paths:
      - findings/
    expire_in: 30 days
  rules:
    - if: '$CI_PIPELINE_SOURCE == "schedule"'

For continuous fuzzing, consider setting up a dedicated server that runs fuzzers 24/7 and reports crashes to your issue tracker via webhooks. Tools like DefectDojo can aggregate fuzzing findings alongside other security scan results for a unified vulnerability management workflow. Pair fuzzing with mutation testing for a comprehensive test quality strategy — while fuzzing discovers unexpected input handling bugs, mutation testing validates that your unit tests actually catch logic errors.

FAQ

What is fuzzing and why is it important for security?

Fuzzing (fuzz testing) is an automated software testing technique that provides invalid, unexpected, or random data as inputs to a program. The program is then monitored for exceptions such as crashes, assertion failures, or memory leaks. Fuzzing has discovered thousands of CVEs in widely-used software and is considered one of the most cost-effective methods for finding security vulnerabilities before they reach production.

Can I fuzz binaries without access to the source code?

Yes. AFL++ supports QEMU mode (afl-fuzz -Q), which uses binary translation to instrument any compiled binary for coverage-guided fuzzing. Honggfuzz also supports black-box fuzzing through its external process mode, though without compile-time coverage feedback. libFuzzer, however, requires source code and LLVM-based compilation.

How long should I run a fuzzing campaign?

Fuzzing is a long-running process. For a small library or parser, a 24-hour campaign can yield meaningful results. For complex applications like network protocol stacks or file format parsers, campaigns often run for weeks or months. The key metric is not time but whether new coverage paths are still being discovered — when the coverage curve plateaus, the campaign has likely exhausted the accessible input space.

How do I integrate fuzzing with my existing self-hosted CI/CD pipeline?

Most self-hosted CI systems (GitLab CI, Gitea Actions, Jenkins) support scheduled pipelines. Configure a nightly or weekly job that: (1) builds the fuzz target with sanitizer instrumentation, (2) runs the fuzzer for a fixed duration with timeout, and (3) uploads any crash artifacts. For continuous fuzzing, run dedicated fuzzing instances on separate hardware and integrate findings into a vulnerability management platform like DefectDojo.

What’s the difference between mutation-based and generation-based fuzzing?

Mutation-based fuzzers (like AFL++, Honggfuzz, and libFuzzer) start with seed inputs and randomly mutate them — flipping bits, inserting bytes, or splicing sections from different inputs. Generation-based fuzzers create inputs from scratch using a grammar or specification of the expected format. Mutation-based fuzzing is easier to set up and works well for most targets, while generation-based fuzzing is more effective for complex structured formats like XML, JSON, or protocol buffers.

Can fuzzing find logic bugs, not just memory corruption?

Traditional fuzzing is excellent at finding memory corruption (buffer overflows, use-after-free) and input parsing bugs. Finding logic bugs (incorrect business logic, authorization bypasses) is harder but possible with techniques like value profiling (tracking which comparison values unlock new code paths) and concolic execution (combining concrete fuzzing with symbolic analysis). AFL++’s Redqueen and MOpt features specifically target comparison-based logic bugs.

Advertise here
Advertise here