# Rust Fuzzing Afl

> Design, implement, run, tune, and triage coverage-guided fuzzing for Rust with cargo-afl and AFL++. Use for fuzz-target creation, corpus and dictionary design, persistent-mode state reset, CmpLog allocation, parallel campaigns, crash minimization, regression tests, CI smoke fuzzing, and AFL troubleshooting.

- Skill: `mnwa/rust-fuzzing-afl` (Agent Skill, multi-file: 28 files)
- Install (CLI): `npx skillmds@latest add mnwa/rust-fuzzing-afl`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mnwa/rust-fuzzing-afl/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: Mnwa (https://skillmd.com/u/mnwa)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mnwa/rust-fuzzing-afl

---


# Rust Fuzzing with AFL

- Version: 1.0.0
- Updated: 2026-08-23
- Reference snapshot: cargo-afl 0.18.x, including `fuzz_with_reset!` and optional omission of CmpLog instrumentation.

## Mission

Build fuzzing that finds real bugs, reproduces them deterministically, and turns every confirmed crash into a regression test.

Treat the harness, oracle, corpus, campaign configuration, and triage path as one system. A fast harness with a weak oracle is incomplete. A strong oracle with hidden global state is unreliable. A crash without a minimized reproducer and regression test is unfinished work.

## Activate this skill when

Use this skill for requests involving:

- Rust fuzzing, AFL, AFL++, `cargo-afl`, or the `afl` crate.
- Parser, decoder, deserializer, decompressor, protocol, bytecode, image, archive, or file-format fuzzing.
- Differential, round-trip, state-machine, no-panic, invariant, or resource-limit fuzzing.
- Native Rust code containing `unsafe`, FFI, custom allocators, SIMD, threading, or manually managed buffers.
- Corpus minimization, dictionaries, multi-core campaigns, CI fuzzing, crash triage, or fuzz regressions.
- Converting an existing `cargo-fuzz` target to AFL, or sharing one target body across engines.

Do not replace focused unit tests or property tests with fuzzing. Use them together.

## Load supporting material selectively

Read only the references needed for the task:

- Harnesses and oracles: `references/harness-design.md`
- Corpus, dictionaries, campaigns, CmpLog, and parallelism: `references/campaign-operations.md`
- Reproduction, minimization, sanitizers, and regressions: `references/triage-and-regressions.md`
- CI, platform constraints, workspaces, and version policy: `references/ci-and-platforms.md`
- Failure diagnosis: `references/troubleshooting.md`
- Primary documentation snapshot: `references/sources.md`

Reusable snippets are under `templates/`. They contain explicit `REPLACE_*` markers and must be adapted to the repository rather than copied blindly.

## Non-negotiable rules

1. **Inspect before editing.** Read the workspace manifests, crate graph, feature flags, MSRV, existing fuzz/property tests, fixtures, public APIs, `unsafe` blocks, FFI boundaries, global state, and likely hot paths.
2. **Isolate fuzz-only dependencies.** Prefer a dedicated non-published package such as `fuzz/afl/`. Do not make `afl` a production dependency.
3. **Keep the target in-process.** Call the library API directly. Do not fuzz through a CLI, shell command, network socket, or production service unless source constraints make that unavoidable.
4. **Make each iteration deterministic.** Control randomness, time, locale, environment, filesystem state, thread scheduling, caches, and static mutable state.
5. **Crash on violated invariants.** Do not catch and discard panics that represent bugs. Use ordinary `afl::fuzz!` unless there is a documented reason for a no-hook variant.
6. **Bound work, not semantics.** Cap pathological allocations, recursion, operation counts, decompressed output, and wall-clock work. Avoid so much pre-validation that malformed inputs never reach the target.
7. **No external side effects.** Never use production credentials, real network endpoints, user home directories, persistent databases, or shared temporary paths. Use isolated per-process temporary state when a filesystem is essential.
8. **Do not invoke privileged tuning automatically.** `cargo afl system-config` may invoke `sudo`. Explain the change and obtain explicit approval before running it.
9. **Preserve evidence.** Do not delete or overwrite a corpus, output directory, or crash artifact without an explicit safe path and a backup or replacement plan.
10. **Finish the loop.** Reproduce, minimize, diagnose, fix, and add a direct regression test. A campaign is not complete merely because a crash file exists.

## Standard workflow

### 1. Establish the target and threat model

Identify:

- The exact API accepting attacker-controlled or untrusted data.
- The reachable implementation, including feature-gated and native dependencies.
- The bugs that matter: panic, memory safety, incorrect acceptance, semantic divergence, unbounded resource use, state corruption, or nondeterminism.
- The maximum meaningful input and output sizes.
- Whether the target has global/static state or spawns threads.
- Whether malformed inputs are expected to return `Err` rather than crash.

Rank candidate targets by:

1. Untrusted input exposure.
2. `unsafe` or FFI density.
3. Parser/decoder complexity.
4. Existing bug history.
5. Lack of test coverage.
6. Cheap in-process execution.
7. Availability of a strong oracle.

Start with a narrow, high-value API. Split unrelated formats or modes into separate targets.

### 2. Choose an oracle before writing the harness

Preferred oracles, in descending order of value:

- **Differential:** two independent implementations agree after normalization.
- **Round-trip:** `decode(encode(x)) == normalize(x)` or `decompress(compress(x)) == x`.
- **Canonicalization/idempotence:** `canonicalize(canonicalize(x)) == canonicalize(x)`.
- **State-machine invariants:** legal operation sequences preserve model constraints.
- **Metamorphic:** transformations preserve a defined result.
- **Resource bound:** output, allocations, recursion, or operations stay within a justified cap.
- **No-panic:** arbitrary input cannot panic, abort, hang, or violate memory safety.

Do not treat every `Err` as a bug. Do not `unwrap()` expected parse failures merely to manufacture crashes.

### 3. Choose the harness architecture

Prefer an engine-neutral target body:

```rust
pub fn fuzz_one(data: &[u8]) {
    // Call the real API and assert the chosen oracle.
}
```

Then keep the AFL adapter thin:

```rust
fn main() {
    afl::fuzz!(|data: &[u8]| {
        fuzz_support::fuzz_one(data);
    });
}
```

This permits deterministic replay from a normal test or binary and makes it easier to add a second engine later.

Use raw `&[u8]` by default. Use a typed `arbitrary::Arbitrary` input when API call sequences or semantically meaningful parameter combinations matter more than malformed byte-level structure.

### 4. Add an isolated AFL package

Recommended layout:

```text
project/
├── Cargo.toml
├── src/
├── tests/
└── fuzz/
    └── afl/
        ├── Cargo.toml
        ├── corpus/
        │   └── target-name/
        ├── dictionaries/
        ├── src/
        │   ├── lib.rs
        │   └── bin/
        │       └── target_name.rs
        └── artifacts/
```

Set `publish = false`. Match the repository's Rust edition and MSRV. Either add the package to the root workspace or make it an intentionally separate nested workspace with an empty `[workspace]` table.

The current `cargo-afl` build path sets fuzzing-oriented code generation options, including debug assertions and overflow checks. Avoid replacing its `RUSTFLAGS` wholesale.

When library code uses `#[cfg(fuzzing)]`, register the custom cfg with the repository's `unexpected_cfgs` lint. `cargo-afl` enables `cfg(fuzzing)` by default across the dependency graph; `AFL_NO_CFG_FUZZING=1` disables that behavior.

### 5. Install and verify the toolchain

Prerequisites include a Rust toolchain, a C compiler, and `make`.

```bash
cargo install cargo-afl --locked
cargo afl --version
```

For a deliberate upgrade:

```bash
cargo install --force cargo-afl --locked
```

After changing the Rust toolchain, rebuild the AFL runtime when required:

```bash
cargo afl config --build
```

Record the Rust version, `cargo-afl` version, bundled AFL++ version, target triple, build features, and relevant environment variables in campaign notes.

### 6. Build and smoke-test the target

From the AFL package:

```bash
cargo afl build --release --bin target_name
```

Verify direct replay before starting a campaign:

```bash
printf 'seed' | target/release/target_name
```

Then run a bounded smoke campaign:

```bash
cargo afl fuzz \
  -i corpus/target-name \
  -o artifacts/target-name \
  -V 60 \
  -- target/release/target_name
```

Before scaling, confirm:

- The executable is instrumented and starts under AFL++.
- At least one seed completes normally.
- Expected invalid inputs return without crashing.
- The map is not empty.
- Execution speed is plausible for the target.
- Stability is high and does not collapse after the first iteration.
- Input size and work limits prevent trivial memory or timeout exhaustion.

### 7. Build the corpus and dictionary

Seed with the smallest diverse inputs that exercise distinct grammar and semantic features:

- Empty or minimum valid input when meaningful.
- One minimal valid example per major mode/version.
- A few near-valid malformed examples.
- Boundary lengths, integer extrema, nesting, repeated sections, and optional fields.
- Previously fixed regression inputs, after reviewing whether they are safe to publish.

Minimize coverage-equivalent seeds:

```bash
cargo afl cmin \
  -i corpus-raw/target-name \
  -o corpus/target-name \
  -- target/release/target_name
```

Use a dictionary for stable protocol tokens, magic values, keywords, delimiters, tags, and field names:

```bash
cargo afl fuzz \
  -i corpus/target-name \
  -o artifacts/target-name \
  -x dictionaries/format.dict \
  -- target/release/target_name
```

Keep tokens short and semantically useful. Do not use a huge dictionary as a substitute for a good harness.

### 8. Run the campaign

A single instance:

```bash
cargo afl fuzz \
  -i corpus/target-name \
  -o artifacts/target-name \
  -- target/release/target_name
```

Resume an existing AFL++ output directory:

```bash
cargo afl fuzz \
  -i - \
  -o artifacts/target-name \
  -- target/release/target_name
```

Use one process per core for serious campaigns. Share one output directory, give every instance a unique `-M` or `-S` name, and vary power schedules or target variants.

`cargo afl fuzz` enables CmpLog by default. Keep CmpLog on at most one or two workers. Pass `-c -` to additional workers. For builds where no worker will use CmpLog, omit its instrumentation entirely:

```bash
AFLRS_NO_CMPLOG=1 cargo afl build --release --bin target_name
```

Do not mix binaries built with incompatible instrumentation or target features in one campaign without documenting the topology.

### 9. Monitor and tune based on evidence

Monitor all workers:

```bash
cargo afl whatsup -s artifacts/target-name
```

Inspect individual `fuzzer_stats`, queue growth, unique crashes, hangs, execution speed, cycles, map density, and stability.

Diagnose before tuning:

- Low stability: leaked state, nondeterminism, threads, uninitialized data, or incomplete persistent reset.
- Very low speed: excessive setup, logging, allocation, large inputs, external I/O, debug wrappers, or an overly broad target.
- No new paths: wrong API, empty instrumentation, seeds rejected too early, hard checksums/encryption/compression barriers, or a weak input model.
- Mostly timeouts/OOM: missing resource bounds or an unrealistic timeout/memory limit.
- Many duplicate crashes: minimize and group by root-cause stack, not filename alone.

Use `fuzz_with_reset!` when persistent iterations reuse static state that can be explicitly cleared. If correct reset is impossible, isolate state per iteration or use a non-persistent adapter rather than accepting poor stability.

### 10. Triage every result

For each crash or hang:

1. Copy the artifact to an immutable triage location.
2. Reproduce with the exact campaign binary and environment.
3. Confirm reproducibility across multiple runs.
4. Minimize with `cargo afl tmin`.
5. Reproduce through the shared non-AFL target body.
6. Obtain a symbolized backtrace.
7. Classify the oracle and root cause.
8. Test under an appropriate sanitizer or Miri when `unsafe`/FFI is implicated.
9. Fix the root cause, not the harness symptom.
10. Add the minimized input as a direct regression test.
11. Re-run the target and relevant test suite.

Example minimization:

```bash
cargo afl tmin \
  -i path/to/crash \
  -o triage/minimized.bin \
  -- target/release/target_name
```

Example direct replay:

```bash
RUST_BACKTRACE=1 target/release/target_name < triage/minimized.bin
```

### 11. Add CI without pretending CI is a full campaign

CI fuzzing should:

- Build every fuzz target.
- Replay all committed regression inputs.
- Run a short bounded AFL++ smoke campaign on selected targets.
- Reuse a small minimized corpus.
- Upload new crash/hang artifacts.
- Fail when a real new crash is found.
- Use deterministic names and avoid privileged host tuning.

Use `-S ci` rather than a main node for a CI worker. In constrained hosted environments, `AFL_SKIP_CPUFREQ=1` and `AFL_NO_AFFINITY=1` may be required. `AFL_FAST_CAL=1` and `AFL_CMPLOG_ONLY_NEW=1` are useful for short runs or large reused corpora.

Long-running campaigns belong on dedicated workers with persisted corpus and output, not solely in pull-request CI.

## Harness choice matrix

| Situation | Preferred input | Primary oracle |
|---|---|---|
| Parser/decoder accepts bytes | `&[u8]` | no-panic + semantic invariants |
| Serializer and parser both exist | typed or bytes | round-trip/canonicalization |
| Rust and reference implementation exist | `&[u8]` or typed | differential |
| Compressor/decompressor | bytes + bounded parameters | round-trip + output cap |
| Stateful API | bounded `Vec<Operation>` | model/state invariants |
| Unsafe/FFI boundary | bytes with explicit size caps | crash + ASan/Miri replay |
| Text grammar | bytes first; `&str` only when required | parser invariants + dictionary |
| Concurrent algorithm | deterministic sequential core first | model; use Loom for schedules |

## Stop conditions

Stop or redesign a target when:

- Stability remains poor after state isolation/reset.
- The harness spends most time in setup, logging, I/O, or unrelated code.
- Nearly all inputs are rejected before reaching meaningful logic.
- The oracle is ambiguous and generates false positives.
- Inputs can trigger unbounded work unrelated to the intended threat model.
- The campaign topology or output directory is not reproducible.
- A crash cannot be replayed outside the fuzzer because the harness hides required state.

Do not silently continue a low-quality campaign.

## Required deliverables

When implementing fuzzing in a repository, produce:

1. A dedicated AFL fuzz package or a documented reason for another layout.
2. One or more focused targets with named oracles.
3. A small reviewed seed corpus.
4. A dictionary when the format has stable tokens.
5. Reproducible build and run commands.
6. A bounded local/CI smoke command.
7. A crash minimization and replay path.
8. Regression tests for confirmed findings.
9. Documentation of version, platform, features, resource limits, and known gaps.
10. Verification results, including anything not run.

## Completion checklist

- [ ] Real API called in-process.
- [ ] Fuzz dependency isolated from production.
- [ ] Target builds with `cargo afl build`.
- [ ] Harness is deterministic and side-effect isolated.
- [ ] Oracle is explicit and low-noise.
- [ ] Input/output/work limits are justified.
- [ ] Seed corpus is small and diverse.
- [ ] Dictionary is present when useful.
- [ ] Persistent state is reset or avoided.
- [ ] CmpLog is limited to one or two workers.
- [ ] Parallel workers use unique names and shared output.
- [ ] Crash replay and minimization commands are documented.
- [ ] Confirmed bugs become direct regression tests.
- [ ] CI smoke run is bounded and preserves artifacts.
- [ ] Toolchain and campaign metadata are recorded.

