Skip to content

crucible-mutator

The cmd/crucible-*-mutator programs build C archives that export libFuzzer custom-mutator entry points for Crucible's structure-aware engines.

Archives

The tree contains archive entry points for GGUF, RPC, protobuf, FlatBuffers, SafeTensors, NumPy, and tokenizer inputs. Build the archive that matches the target format; an archive for one format is not a generic structured mutator for all inputs.

Example for GGUF:

CGO_ENABLED=1 go build -buildmode=c-archive \
  -o harness/libfuzzer/libcruciblemut.a ./cmd/crucible-mutator

This emits two files next to each other, libcruciblemut.a and libcruciblemut.h. The header declares the exported entry points for the C side of the harness; target-specific build scripts supply the actual compiler and link flags.

Developer-only. These archives are not produced by make build and are not part of the supported public surface. They exist for building instrumented harnesses against a target you supply. See Support labels.

Exported entry points

LLVMFuzzerCustomMutator

size_t LLVMFuzzerCustomMutator(
    uint8_t *data, size_t size, size_t max_size, unsigned int seed);

For the GGUF archive, the function copies the input, attempts MutateBytes, truncates a successful result to max_size, and copies it back. If parsing or structured mutation declines, it delegates to libFuzzer's LLVMFuzzerMutate.

The archive includes a weak LLVMFuzzerMutate stub so non-fuzzer builds can link. If that stub wins inside a real harness, delegation returns zero and the old no-mutation failure returns. Therefore linkage must be verified, not assumed.

LLVMFuzzerCustomCrossOver

size_t LLVMFuzzerCustomCrossOver(
    const uint8_t *data1, size_t size1,
    const uint8_t *data2, size_t size2,
    uint8_t *out, size_t max_out_size,
    unsigned int seed);

The GGUF implementation attempts structural crossover, then structured mutation of the first input, then a bounded verbatim copy only if both structured operations fail. Crossover does not currently delegate to libFuzzer's byte mutator because it writes to a distinct output buffer.

Linkage is a correctness property

libFuzzer only uses these functions if the final executable retains their symbols. Static archives can be dropped when nothing creates a strong reference, so build scripts force-link the custom entry point and Crucible's linkage gate inspects the final harness.

Run the project check after building harnesses:

make verify-mutators

That target runs tools/verify-mutators.sh, which inspects the built harnesses for the custom symbol. A missing harness is CANNOT TELL, not evidence that linkage is correct.

Earlier revisions named a target that does not exist

This page previously said make mutator-linkage-gate and hedged that "the exact target name is defined by the Makefile". No such target exists, so the instruction could not work. The Makefile defines verify-mutators.

Campaign controls

For a structured-versus-byte comparison:

  1. build both harnesses from the same target objects;
  2. prove the custom symbols are present in one and absent in the other;
  3. use content-identical seed corpora, the same libFuzzer seed and limits, and independent fresh processes;
  4. bank both binaries and their hashes; and
  5. compare Exact identities within a build and Stable identities only across changed builds.

Do not describe an archive as active because it was compiled. The final executable is the evidence.