Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Structured fuzzers

The structured fuzzer suite models a curl transfer with one annotated protobuf authoring schema. The build derives a restricted root for each target. libprotobuf-mutator mutates that target-specific type rather than an unstructured byte stream, and a generated adapter lowers it into the common runtime before the harness runs curl against a bounded local peer.

The targets still consume ordinary binary protobuf files. Textprotos under scenarios/curl_fuzzer_proto/ are readable full-authoring seed sources; CMake encodes them under build/generated_corpora/.

How an input reaches curl

flowchart TD
    subgraph build["Build time"]
        sources["Annotated authoring schema<br/>eligibility metadata<br/>selected curl.h"]
        descriptor["Descriptor set"]
        generator["Validate one eligibility model"]
        authoring["Metadata-free authoring schema"]
        runtimeSchema["Internal runtime schema<br/>and option manifest"]
        profileSchema["Restricted profile schema<br/>and direct Traits adapter"]
        seeds["Checked-in .textproto seeds"]
        encoder["ordinary protoc --encode<br/>authoring Scenario"]
        corpus["Per-target .scenario corpus"]
        target["Structured fuzzer executable<br/>linked to one generated profile"]

        sources --> descriptor --> generator
        generator --> authoring
        generator --> runtimeSchema
        generator --> profileSchema
        authoring --> encoder
        seeds --> encoder --> corpus
        runtimeSchema --> target
        profileSchema --> target
    end

    subgraph runtime["Each fuzz or replay iteration"]
        input["Binary profile-compatible input"]
        lpm["libprotobuf-mutator<br/>generated profile root"]
        lower["Direct lower to internal Scenario"]
        fixed{"Fixed profile?"}
        normalize["Normalize selectors<br/>generated prune<br/>bound surviving shape"]
        runner["Scenario runner"]
        curl["curl"]
        peer["Harness-owned local peer"]

        input --> lpm --> lower --> fixed
        fixed -->|yes| normalize --> runner
        fixed -->|compatibility| runner
        runner --> curl
        runner --> peer
        curl <-->|bounded protocol exchange| peer
    end

    corpus --> input
    target --> input

SetOption is an ordered oneof of typed fields. Each field declares its stable protobuf tag, native CURLOPT_* identity, and profile membership beside the value. Structured fields carry the same kind of eligibility metadata. The generator checks those declarations against the selected curl checkout’s curl.h, then derives the internal CurlOptionId, value-kind manifest, profile cases, and direct transfers. There is no parallel handwritten C++ option allowlist.

The metadata-free authoring view is staged at build/schemas/curl_fuzzer.proto. Normal checked-in textprotos are encoded against that full view with ordinary protoc --encode. Generated restricted roots keep the same field numbers and wire types, so a target can parse those bytes directly and discard unknown fields. No separate per-profile seed encoder is required.

Each thin entrypoint binds its generated Traits once into an ordinary kProfile descriptor and passes that descriptor to non-template entrypoint helpers. Mutation, crossover, and loading therefore use the same concrete message type without exposing template specializations to Fuzz Introspector. Static profile projection prevents unsupported fields from entering the mutation graph. Generated conditional pruning runs after selector normalization and before limits, while C++ continues to own scheme fixing, selector canonicalization, work bounds, resource lifetimes, and peer behavior. The compatibility target uses a generated broad root and keeps its historical mixed execution mode; the schema format itself still changed in place.

Pre-cutover binaries are not automatically detected or loaded. Reproduce them with the revision and target that created them.

The peers are owned by the harness. Stream protocols generally use local socket pairs; TFTP and HTTP/3 use private loopback UDP endpoints. Baseline curl configuration disables ambient proxies, restricts protocols, redirects direct connections to the harness, and uses short timeouts. A new target or option must preserve those isolation guarantees.

Build and replay a scenario

Build one structured target and its generated seed corpus:

./mainline.sh -t curl_fuzzer_proto_http

Replay one seed with the standalone runner:

./build/curl_fuzzer_proto_http \
  build/generated_corpora/curl_fuzzer_proto_http/basic_get.scenario

The standalone executable accepts files and directories, so an entire target-specific seed set can be replayed too:

./build/curl_fuzzer_proto_http \
  build/generated_corpora/curl_fuzzer_proto_http/

Set FUZZ_VERBOSE=1 when a reproduction needs curl’s protocol trace. These locally built executables replay inputs; OSS-Fuzz builds provide the active libFuzzer mutation engine.

Continue with Writing and inspecting scenarios, then consult Target profiles before choosing a corpus or adding a field. The C++ reference documents the implementation’s classes, functions, and ownership contracts.