# Coverage-Guided Fuzzing

The default algorithm of any modern fuzzer worth using. Treat the set of test cases as an evolving population, the SUT's execution paths as the fitness landscape, and "reached new coverage" as the selection criterion. Test cases that exercise new code stay in the corpus and get mutated further; offspring that don't add coverage are discarded.

From the [[appsec-guide-fuzzing-handbook|Trail of Bits Testing Handbook]]:

```
fn fuzz(corpus) {
  let bug_set = [];
  while let Some(test_case) = schedule(corpus) {
    let offspring = mutate(test_case);
    let observations = execute(offspring);

    if (is_interesting(observations)) { corpus.append(offspring); }
    if (is_bug(observations))         { bug_set.append(offspring); }
  }
  return bug_set;
}
```

Four customization points, in roughly increasing order of how much they matter to a campaign:

- `mutate` — bit flips, byte inserts/deletes, splices between corpus entries, dictionary-token insertion. Most fuzzers ship a fixed stack of mutators; advanced engines let you compose your own.
- `is_interesting` — almost always "did the run hit an edge not in the coverage map yet"; some engines also track comparison-operand state (libFuzzer's `value-profile`) or function-call-context coverage
- `schedule` — picks which corpus entry to mutate next. AFL's "favored seeds" prioritization is the canonical example; LibAFL exposes scheduling as a trait
- `execute` — runs the SUT under instrumentation; the engine's I/O hot path lives here

## Why coverage works as fitness

Coverage is a cheap proxy for "the input exercises code the previous corpus did not." That's almost the right thing — the *true* objective is "find inputs that reveal bugs," but bugs cluster in under-exercised code, and edge coverage correlates closely enough with reachability that the heuristic produces the bug counts the field is famous for. It does mean coverage-guided fuzzing systematically misses bugs in *fully covered* code (e.g., subtle data-dependent bugs in a path the corpus reaches but doesn't stress).

## What instrumentation makes this work

The fuzzer needs to know which edges executed. Two paths:

- **Compile-time** — `-fsanitize=fuzzer` (Clang) or AFL's wrapper compilers insert a per-edge counter. Cheap at runtime, requires building the SUT.
- **Binary-only** — QEMU mode, Intel PT, Frida-mode. Slower (10-100×), but doesn't need source. Used when fuzzing closed-source targets or compiled deps.

## How this composes with other techniques

- [[grammar-based-fuzzing]] — substitutes `mutate` for a grammar-aware tree mutator; coverage feedback still works the same
- [[structure-aware-fuzzing]] — wraps the input in a decoder; the corpus is bytes, the SUT sees typed values
- [[differential-fuzzing]] — replaces `is_bug` with "implementation A's output ≠ implementation B's output"; coverage still drives `is_interesting`

## Engines

- **libFuzzer** — in-process, fastest, requires source. The `LLVMFuzzerTestOneInput` API.
- **AFL++** — out-of-process, broader sanitizer/coverage feature set, the descendant of original AFL
- **[[libafl|LibAFL]]** — library of composable parts; build your own fuzzer
- **Honggfuzz** — Google fuzzer with PT/Intel BTS and persistent mode
- **Engine-specific specializations** — kAFL (kernel fuzzing via Intel PT in KVM), Syzkaller (Linux syscall fuzzing), AFLNet (stateful protocol fuzzing)
