Skip to content

Writing Fuzz Harnesses

A harness defines what Crucible can see. The best harness is the smallest wrapper around the real entry point that preserves natural validation, allocation, and consumer behavior.

Harness contract

A reviewable harness must state:

  • target repository and commit;
  • exact entry point and why an attacker-controlled artifact reaches it;
  • build flags and sanitizer runtime;
  • whether it parses only, allocates, or executes a natural consumer;
  • resource limits and timeout behavior;
  • control input and expected outcome;
  • whether it intercepts assertions or other faults; and
  • any behavior deliberately stubbed or skipped.

Prefer a path ladder

Build depth deliberately:

  1. Parser harness for framing and metadata.
  2. Allocation harness for size, offset, and lifetime behavior.
  3. Natural-consumer harness for kernels or inference paths that consume the loaded object.
  4. Stock artifact replay for the shipped binary or library.

no_alloc=true is a useful fast parser harness, but it does not exercise all parsing-related bugs. It can skip allocation checks, payload reads, and consumers that turn a corrupted object into an out-of-bounds access. Label the rung instead of treating the shallow harness as complete.

File-argument libFuzzer shape

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < minimum_header_size) {
        return 0;
    }

    TempFile input(data, size);        // checked write, unique path, RAII cleanup
    auto result = target_load(input.path());
    if (!result) {
        return 0;
    }

    exercise_natural_consumer(*result);
    return 0;
}

Use checked writes and RAII cleanup. A constant temporary filename creates cross-worker races. Do not catch target exceptions merely to keep fuzzing unless the campaign explicitly classifies them as infrastructure and preserves the evidence; swallowed failures create false clean exits.

Sanitizers are measured capabilities

Typical Clang flags are:

-g -O1 -fno-omit-frame-pointer \
-fsanitize=fuzzer,address,undefined

Linkage alone is not proof that a sanitizer works. crucible doctor uses a deliberate fault canary for the local toolchain. For remote or custom builds, run equivalent behavioral canaries and record which classes were actually compiled and observed. ASan does not detect uninitialized reads; use an appropriate tool or a direct byte-comparison oracle for that class.

Assertions and intercepted faults

If a replay harness intentionally intercepts a target assertion, declare that capability in a HarnessManifest bound to the binary SHA-256. Without a valid manifest, assertion text from a surviving process is diagnostic evidence, not proof that the process crashed. Rebuilding the binary invalidates the old declaration.

Controls before campaigns

Every new harness should have:

  • a known-good control that exits cleanly;
  • a known-positive canary when one exists;
  • a missing/invalid input that proves failure-to-start and loader errors are distinguishable;
  • a timeout case that proves the process group is killed; and
  • a test showing stdout/stderr and process facts are banked together.

Then run:

crucible harness-smoke --harness ./target-fuzz --seed ./control.bin
crucible preflight --harness ./target-fuzz --control ./control.bin

Custom-mutator linkage

When linking a Crucible C archive, force-link the custom entry point and run the linkage gate against the final executable. A static archive on the command line can still be discarded. For an A/B comparison, prove the symbol is present in the structured arm and absent from the byte-only arm.

What a harness result can claim

  • A parser-only sanitizer trace establishes the instrumented code path faulted.
  • A natural consumer strengthens consequence and reachability.
  • A stock artifact establishes the shipped path for that build.
  • None of them alone establishes affected versions, exploitability, or deployment exposure.

Record the rung in the finding instead of collapsing every witness into “end to end.”