libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

README (24536B)


      1 GNU libmicrohttpd -- OSS-Fuzz integration
      2 ========================================
      3 
      4 This directory contains everything needed to run the in-process fuzzing
      5 harnesses of `src/fuzz/` on Google's OSS-Fuzz service: the build script,
      6 the project metadata, the container recipe, the seed-corpus packager,
      7 per-harness dictionaries and per-harness `.options` files.
      8 
      9 It is the "continuous" half of `TESTING.md` section **P4**; `src/fuzz/`
     10 is the other half.  Read `src/fuzz/README` first -- it explains what the
     11 four harnesses do, what they have already found, and how to run them
     12 without clang.
     13 
     14 
     15 -------------------------------------------------------------------
     16 0. This is deliberately NOT part of contrib/ci/
     17 -------------------------------------------------------------------
     18 
     19 **OSS-Fuzz is not a CI job and nothing here is wired into
     20 `contrib/ci/jobs/`.**  That is on purpose, at the maintainer's request:
     21 
     22   * an OSS-Fuzz run is a *continuous, hosted, unbounded* campaign owned
     23     by Google's infrastructure, not a bounded per-push check.  A CI job
     24     must terminate in minutes and must be reproducible offline; neither
     25     is true here;
     26   * the artifacts in this directory are consumed by the
     27     `google/oss-fuzz` repository (`projects/libmicrohttpd/`), not by
     28     this tree's build system;
     29   * running these builds needs Docker, the OSS-Fuzz base images and
     30     network access.
     31 
     32 The bounded, in-tree fuzzing that *does* belong in CI already exists and
     33 is unrelated to this directory: `make -C src/fuzz check` (a few seconds
     34 per harness) and `make -C src/fuzz check-corpus` (corpus replay).  Wire
     35 *those* into `contrib/ci/`, never this.
     36 
     37 Nothing in this directory is referenced by any `Makefile.am`; adding it
     38 to the build system is not required and not wanted.
     39 
     40 
     41 -------------------------------------------------------------------
     42 1. Layout
     43 -------------------------------------------------------------------
     44 
     45     build.sh                    the OSS-Fuzz build script
     46     project.yaml                OSS-Fuzz project metadata
     47     Dockerfile                  the base-builder container recipe
     48     make_seed_corpus.sh         packages src/fuzz/corpus/ into
     49                                 $OUT/<fuzzer>_seed_corpus.zip
     50     fuzz_request.options        per-harness libFuzzer options (max_len, dict)
     51     fuzz_str.options
     52     fuzz_auth_header.options
     53     fuzz_postprocessor.options
     54     dicts/fuzz_request.dict     per-harness fuzzing dictionaries
     55     dicts/fuzz_str.dict
     56     dicts/fuzz_auth_header.dict
     57     dicts/fuzz_postprocessor.dict
     58     README                      this file
     59 
     60 Of these, only `build.sh`, `project.yaml` and `Dockerfile` are copied
     61 into `google/oss-fuzz`; everything else is read out of the cloned
     62 libmicrohttpd checkout at build time, which is why the dictionaries and
     63 the corpus packager live here and not in the OSS-Fuzz repository.
     64 
     65 
     66 -------------------------------------------------------------------
     67 2. The four fuzz targets
     68 -------------------------------------------------------------------
     69 
     70     fuzz_request            a real struct MHD_Daemon driven over a
     71                             socketpair; consumes daemon options *and*
     72                             stream segmentation from the input.  Bytes
     73                             4-9 (see src/fuzz/README section 2.2.1)
     74                             additionally select the response
     75                             constructor, the authentication entry point,
     76                             the event-loop API, and whether the
     77                             connection is suspended or upgraded
     78     fuzz_str                the mhd_str.c primitives with exactly-sized
     79                             output buffers
     80     fuzz_auth_header        MHD_get_rq_dauth_params_() /
     81                             MHD_get_rq_bauth_params_()
     82     fuzz_postprocessor      MHD_post_process()
     83 
     84 All four are single translation units that export
     85 
     86     int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size);
     87 
     88 unconditionally.  `-DFUZZ_NO_MAIN` (which `build.sh` passes) removes only
     89 the standalone driver's `main()` from `fuzz_common.h`; the fuzz target
     90 itself is never conditionally compiled.  There is no
     91 `LLVMFuzzerInitialize()` and none is needed: the two tuning knobs of
     92 `fuzz_request` (`MHD_FUZZ_MIN_DISCIPLINE`, `MHD_FUZZ_MIN_MEM_LIMIT`) are
     93 read lazily with `getenv()` on the first call and default to the *full*
     94 range (-3 and 0), which is what a fuzzing service should explore.
     95 
     96 ### max_len
     97 
     98 `max_len` in the `.options` files is set to the point beyond which the
     99 harness ignores the extra bytes, so libFuzzer does not waste its budget:
    100 
    101     fuzz_request        8192   10 configuration bytes + length-prefixed
    102                                send segments (2-byte header, payload <= 0x3FFF,
    103                                at most 96 segments over at most 8
    104                                connections).  8 KiB comfortably holds the
    105                                two-request %%NONCE%% digest handshake, a
    106                                chunked body with extensions and trailers,
    107                                and a body-oracle declaration.  Larger
    108                                inputs are safe -- every segment is bounded
    109                                independently -- but buy almost nothing.
    110     fuzz_str             514   2 selector bytes + the payload, which the
    111                                harness truncates to 512.
    112     fuzz_auth_header    4097   1 selector byte + the Authorization header
    113                                value, truncated to 4096.
    114     fuzz_postprocessor  8196   4 selector bytes + the POST body, truncated
    115                                to 8192.
    116 
    117 ### rss_limit_mb, and why a long run creeps
    118 
    119 libFuzzer's default `-rss_limit_mb=2048` counts the whole process, and an
    120 ASan-instrumented target grows slowly over hundreds of millions of
    121 executions even with no leak at all: ASan's allocator keeps a quarantine
    122 of freed chunks and does not return memory to the OS eagerly.  A local
    123 two-hour campaign hit exactly this -- `fuzz_options` crept from 686 MB to
    124 1804 MB over 26M executions and was killed as an OOM, while
    125 LeakSanitizer stayed silent across 3M-execution runs and
    126 `MALLOC_ARENA_MAX` changed nothing (it is glibc's knob, and none of these
    127 allocations go through glibc).
    128 
    129 Three arms of the same target over the same corpus, 4M executions each,
    130 settle it:
    131 
    132     ASan, default quarantine                  608 MB
    133     ASan, ASAN_OPTIONS=quarantine_size_mb=1   266 MB
    134     no sanitizer at all (glibc allocator)      36 MB
    135 
    136 17x between the first and the last, with no code difference: the growth
    137 is the sanitizer's allocator holding freed memory, not the harness and
    138 not MHD.
    139 
    140 So treat a slow, monotonic RSS climb as allocator retention rather than a
    141 finding.  ClusterFuzz restarts its fuzzers periodically, which bounds it;
    142 do the same locally rather than raising the limit forever.
    143 
    144 ### close_fd_mask
    145 
    146 Deliberately **not** set.  The harnesses are quiet by default (MHD's
    147 error log is only enabled when `MHD_FUZZ_VERBOSE` is set, which never
    148 happens under libFuzzer), so there is no output to suppress -- while
    149 `fuzz_report_finding()` writes the description of a non-memory-safety
    150 finding straight to fd 2 and `MHD_PANIC()` writes to stderr too.  Closing
    151 those fds would throw away exactly the diagnostics that make a report
    152 actionable.
    153 
    154 
    155 -------------------------------------------------------------------
    156 3. What build.sh configures, and why
    157 -------------------------------------------------------------------
    158 
    159     --enable-static --disable-shared --with-pic
    160         fuzz_str and fuzz_auth_header call MHD-internal symbols compiled
    161         with hidden visibility; they are not exported from
    162         libmicrohttpd.so and can only be reached through the static
    163         archive.  OSS-Fuzz also requires the binaries in $OUT to be
    164         self-contained.
    165     --enable-fuzzing
    166         configures src/fuzz/Makefile.  Not strictly needed (build.sh
    167         compiles the harnesses itself) but it keeps this build equivalent
    168         to the documented developer build and fails loudly if src/fuzz/
    169         ever stops being wired into configure.ac.
    170     --enable-asserts
    171         keeps mhd_assert() alive.  Assertions reachable from network input
    172         are remote aborts; findings K1-K7 in `src/fuzz/README` section 6
    173         are all of that kind and are invisible without this.
    174     --disable-https
    175         the harnesses never speak TLS (they hand MHD an already-connected
    176         AF_UNIX socketpair through MHD_add_connection() and never set
    177         MHD_USE_TLS), so HTTPS adds no coverage; it would drag in GnuTLS,
    178         which OSS-Fuzz fuzzes separately, and it would make the
    179         MemorySanitizer build impossible without an MSan-instrumented
    180         GnuTLS.  With HTTPS off, libc and libpthread are the only
    181         external dependencies.
    182     --disable-curl --disable-doc --disable-examples --disable-tools
    183         not needed, and they only add build time and dependencies.
    184     --enable-build-type=neutral
    185         the default, stated explicitly: "neutral" is the only build type
    186         that does not inject its own optimisation/debug flags, so the
    187         $CFLAGS supplied by OSS-Fuzz survive unmodified.
    188 
    189 `build.sh` never sets `--enable-sanitizers` or `--enable-coverage`:
    190 OSS-Fuzz provides instrumentation through `$CFLAGS`/`$CXXFLAGS`, and a
    191 second, configure-generated `-fsanitize=` set is a classic way to break
    192 an OSS-Fuzz build.  `$CFLAGS` is passed through to `configure` and to
    193 every harness compilation verbatim.
    194 
    195 The build is out-of-tree (`$WORK/mhd-build`), so `build.sh` never
    196 modifies the checkout.  The git checkout ships no `configure`, so
    197 `./bootstrap` runs first; because `bootstrap` ends in an `|| echo ...`
    198 chain and therefore cannot be trusted to return a failure status,
    199 `build.sh` checks for the product and falls back to `autoreconf -fi`.
    200 
    201 Note that out-of-tree does *not* mean the checkout can be a tree you
    202 have already configured in place: an in-tree `config.status` makes any
    203 subsequent out-of-tree `configure` stop with "source directory already
    204 configured; run \"make distclean\" there first".  Either `make
    205 distclean` the checkout or -- better, and closer to what OSS-Fuzz
    206 actually does -- point `$MHD_SRC` at a pristine clone:
    207 
    208     git clone --shared /path/to/libmicrohttpd /tmp/mhd-fuzz-src
    209 
    210 
    211 ### $CFLAGS / $CXXFLAGS on a local run
    212 
    213 Under OSS-Fuzz these are always exported by the base-builder image and
    214 `build.sh` uses them verbatim.  When they are unset -- i.e. only on a
    215 local run -- `build.sh` derives them from `$SANITIZER`.  Two flags are
    216 what make such a run a real fuzzing run rather than a build that merely
    217 succeeds:
    218 
    219   * `-fsanitize=fuzzer-no-link` installs libFuzzer's coverage
    220     instrumentation into every translation unit of the library.  Without
    221     it there is no feedback signal at all: libFuzzer degenerates to blind
    222     random generation, `cov:` never moves and the corpus never grows.
    223     This is the single easiest thing to leave out, and nothing about the
    224     resulting build looks wrong.
    225   * a sanitizer, because libFuzzer on its own only notices crashes the
    226     kernel delivers.  (MHD's own oracles -- the `MHD_set_panic_func()`
    227     tripwire and `mhd_assert()` -- do fire without one, which is why
    228     `SANITIZER=none` is still worth something.)
    229 
    230 The `address` default folds UBSan into the ASan build, with
    231 `-fno-sanitize-recover=undefined` so that UB actually kills the process
    232 instead of printing and continuing.  That differs deliberately from
    233 OSS-Fuzz, which runs `address` and `undefined` as two separate
    234 campaigns: it has unlimited machine time and wants each report
    235 attributed to one sanitizer, whereas a local run has an afternoon and is
    236 better off with two oracles per CPU-hour.  Pass `SANITIZER=undefined`
    237 explicitly to get the split OSS-Fuzz shape.
    238 
    239 `SANITIZER=memory` is accepted but is only meaningful inside the
    240 OSS-Fuzz image, where the C library is instrumented too; on a distro
    241 toolchain it reports uninitialised reads in libc frames.
    242 
    243 A complete local run then needs nothing but clang and the compiler-rt
    244 runtimes (on Debian: `clang`, `libclang-rt-dev`, and `llvm` for
    245 `llvm-symbolizer`, without which every trace is bare addresses):
    246 
    247     git clone --shared . /tmp/mhd-fuzz-src
    248     WORK=/tmp/mhd-fuzz-work OUT=/tmp/mhd-fuzz-out MHD_SRC=/tmp/mhd-fuzz-src \
    249       /tmp/mhd-fuzz-src/contrib/oss-fuzz/build.sh
    250     mkdir /tmp/c && unzip -q /tmp/mhd-fuzz-out/fuzz_request_seed_corpus.zip -d /tmp/c
    251     /tmp/mhd-fuzz-out/fuzz_request /tmp/c \
    252         -dict=/tmp/mhd-fuzz-out/fuzz_request.dict -max_len=8192 \
    253         -jobs=8 -workers=8 -max_total_time=3600
    254 
    255 `build.sh` prints the resolved `CC`, `CXX`, `CFLAGS` and `CXXFLAGS` in
    256 its banner; check there first if a local run finds nothing.
    257 
    258 
    259 -------------------------------------------------------------------
    260 4. Building and running locally with infra/helper.py
    261 -------------------------------------------------------------------
    262 
    263 Prerequisites: Docker, python3, and a checkout of `google/oss-fuzz`.
    264 
    265     git clone --depth 1 https://github.com/google/oss-fuzz
    266     cd oss-fuzz
    267     mkdir -p projects/libmicrohttpd
    268     cp /path/to/libmicrohttpd/contrib/oss-fuzz/build.sh      projects/libmicrohttpd/
    269     cp /path/to/libmicrohttpd/contrib/oss-fuzz/project.yaml  projects/libmicrohttpd/
    270     cp /path/to/libmicrohttpd/contrib/oss-fuzz/Dockerfile    projects/libmicrohttpd/
    271 
    272 Build the image and the targets:
    273 
    274     python3 infra/helper.py build_image libmicrohttpd
    275     python3 infra/helper.py build_fuzzers --sanitizer address libmicrohttpd
    276     python3 infra/helper.py check_build libmicrohttpd
    277 
    278 `build_fuzzers` accepts an optional path to a local source tree as its
    279 last argument, which is how you test uncommitted changes:
    280 
    281     python3 infra/helper.py build_fuzzers --sanitizer address \
    282         libmicrohttpd /path/to/libmicrohttpd
    283 
    284 Repeat for the other sanitizers, engines and architectures before
    285 submitting:
    286 
    287     python3 infra/helper.py build_fuzzers --sanitizer undefined    libmicrohttpd
    288     python3 infra/helper.py build_fuzzers --sanitizer memory       libmicrohttpd
    289     python3 infra/helper.py build_fuzzers --engine afl             libmicrohttpd
    290     python3 infra/helper.py build_fuzzers --engine honggfuzz       libmicrohttpd
    291     python3 infra/helper.py build_fuzzers --architecture i386      libmicrohttpd
    292 
    293 
    294 ### 4.1 Without Docker
    295 
    296 `build.sh` also runs directly, and it reads the same four variables
    297 OSS-Fuzz sets, so the whole matrix is reachable on a developer machine:
    298 
    299     SANITIZER        address | undefined | memory | coverage | none
    300     FUZZING_ENGINE   libfuzzer | afl | honggfuzz | none
    301     ARCHITECTURE     x86_64 | i386
    302 
    303 Every combination has been built and driven locally.  The recipes:
    304 
    305     # libFuzzer, x86_64, ASan+UBSan  (the default)
    306     MHD_SRC=/path/to/src WORK=/tmp/w OUT=/tmp/o ./contrib/oss-fuzz/build.sh
    307 
    308     # i386.  Needs gcc-multilib and a 32 bit libstdc++; build.sh adds
    309     # -m32 -no-pie itself.
    310     ARCHITECTURE=i386 MHD_SRC=... WORK=... OUT=... ./build.sh
    311 
    312     # AFL++  (Debian: apt install afl++)
    313     FUZZING_ENGINE=afl MHD_SRC=... WORK=... OUT=... ./build.sh
    314     afl-fuzz -i seeds -o findings -- $OUT/fuzz_request
    315 
    316     # honggfuzz.  Not packaged by Debian; build it and put its compiler
    317     # wrappers on $PATH:
    318     #   git clone https://github.com/google/honggfuzz /tmp/honggfuzz
    319     #   make -C /tmp/honggfuzz
    320     PATH=/tmp/honggfuzz/hfuzz_cc:$PATH FUZZING_ENGINE=honggfuzz \
    321       MHD_SRC=... WORK=... OUT=... ./build.sh
    322     /tmp/honggfuzz/honggfuzz -i seeds -o findings -- $OUT/fuzz_request
    323 
    324     # no engine at all: keeps the harnesses' own deterministic driver, so
    325     # this is a sanitizer smoke test that needs nothing installed.
    326     FUZZING_ENGINE=none MHD_SRC=... WORK=... OUT=... ./build.sh
    327     $OUT/fuzz_request --iterations=100000 --seed=1
    328 
    329 Local toolchain quirk, not a property of the build: Debian's clang picks
    330 a gcc installation that may have no matching libstdc++, and the link then
    331 fails with `cannot find -lstdc++`.  Pin it, choosing a gcc that has the
    332 word size you are building for:
    333 
    334     CXX="clang++ --gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/15"   # 64 bit
    335     CXX="clang++ --gcc-install-dir=/usr/lib/gcc/x86_64-linux-gnu/14"   # 32 bit
    336 
    337 Not set inside build.sh on purpose: in the OSS-Fuzz image clang uses its
    338 own libc++ and any such pin would be wrong.
    339 
    340 Run one:
    341 
    342     python3 infra/helper.py run_fuzzer libmicrohttpd fuzz_request
    343     python3 infra/helper.py run_fuzzer libmicrohttpd fuzz_str
    344     python3 infra/helper.py run_fuzzer libmicrohttpd fuzz_auth_header
    345     python3 infra/helper.py run_fuzzer libmicrohttpd fuzz_postprocessor
    346 
    347 `run_fuzzer` passes anything after the target name to libFuzzer, and
    348 takes `--corpus-dir` for a persistent corpus:
    349 
    350     python3 infra/helper.py run_fuzzer --corpus-dir=/tmp/mhd-corpus \
    351         libmicrohttpd fuzz_request -- -max_total_time=600 -rss_limit_mb=4096
    352 
    353 Coverage report (needs a coverage build):
    354 
    355     python3 infra/helper.py build_fuzzers --sanitizer coverage libmicrohttpd
    356     python3 infra/helper.py coverage libmicrohttpd --fuzz-target fuzz_request
    357 
    358 
    359 -------------------------------------------------------------------
    360 5. What to do with a report
    361 -------------------------------------------------------------------
    362 
    363 An OSS-Fuzz report contains a *testcase* (the raw input bytes) and a
    364 stack trace.  Download the testcase from the report, then:
    365 
    366     python3 infra/helper.py reproduce libmicrohttpd fuzz_request ./testcase
    367 
    368 The same bytes can also be replayed with the in-tree standalone driver,
    369 which needs no Docker and no clang:
    370 
    371     ./configure --enable-fuzzing --enable-static --enable-asserts \
    372                 --enable-sanitizers=address,undefined
    373     make -C src/fuzz check_PROGRAMS      # or: make -C src/fuzz check
    374     src/fuzz/fuzz_request --file=./testcase
    375 
    376 Minimising a libFuzzer crash:
    377 
    378     python3 infra/helper.py shell libmicrohttpd
    379     # inside the container:
    380     /out/fuzz_request -minimize_crash=1 -runs=100000 /testcase
    381 
    382 Once fixed, add the minimised input to `src/fuzz/corpus/` (or to
    383 `src/fuzz/corpus/known-findings/` if it stays interesting as a named
    384 regression) and commit it: `make_seed_corpus.sh` picks it up
    385 automatically on the next OSS-Fuzz build, so the case is re-run forever.
    386 
    387 Note that `fuzz_request` is **not** perfectly deterministic: MHD's digest
    388 nonces embed a millisecond timestamp, and whether a nonce counts as stale
    389 therefore depends on the wall clock.  Finding K6 is of that kind and
    390 reproduces in only a fraction of replays.  If ClusterFuzz marks a
    391 testcase "unreproducible" but the trace points at nonce handling, replay
    392 it in a loop before dismissing it.
    393 
    394 
    395 -------------------------------------------------------------------
    396 6. Seed corpora
    397 -------------------------------------------------------------------
    398 
    399 `make_seed_corpus.sh` builds one zip per target:
    400 
    401     $OUT/fuzz_request_seed_corpus.zip
    402     $OUT/fuzz_str_seed_corpus.zip
    403     $OUT/fuzz_auth_header_seed_corpus.zip
    404     $OUT/fuzz_postprocessor_seed_corpus.zip
    405 
    406 `src/fuzz/corpus/` is organised per harness by file-name prefix
    407 (`fuzz_<harness>-NN.bin`); inputs are *not* interchangeable between
    408 harnesses, because byte 0 selects a different thing in each, so each zip
    409 gets only its own prefix.  `src/fuzz/corpus/README` is documentation and
    410 is excluded.
    411 
    412 `src/fuzz/corpus/known-findings/K*.bin` are byte-exact reproducers for
    413 the findings documented in `src/fuzz/README` section 6.  They are all
    414 `fuzz_request` inputs and are added to that target's seed corpus
    415 (prefixed `known-finding-`), which is what turns them into permanent
    416 regression tests: ClusterFuzz keeps every seed in the corpus and replays
    417 it on every run.
    418 
    419 Reproducers of findings that are still *open* are skipped.  A finding is
    420 open exactly when `patches/$ID.diff` exists, and its reproducer crashes
    421 the target by construction, so shipping it would make every ClusterFuzz
    422 run open by rediscovering a bug that is already written down.  Committing
    423 the fix means deleting the diff, and that alone promotes the reproducer
    424 to a shipped regression seed.
    425 
    426 Nothing is open at the time of writing, so `patches/` does not exist and
    427 all eight reproducers ship.
    428 
    429 Regenerating the corpus from the harnesses' built-in seeds:
    430 
    431     make -C src/fuzz refresh-corpus     # ./fuzz_<name> --write-corpus=corpus
    432 
    433 That only rewrites the `fuzz_<harness>-NN.bin` files.  `known-findings/`
    434 is hand-maintained and is never touched by it.
    435 
    436 The script can be run by hand:
    437 
    438     contrib/oss-fuzz/make_seed_corpus.sh . /tmp/out
    439     unzip -l /tmp/out/fuzz_request_seed_corpus.zip
    440 
    441 
    442 -------------------------------------------------------------------
    443 7. Dictionaries
    444 -------------------------------------------------------------------
    445 
    446 `dicts/*.dict` are libFuzzer/AFL dictionaries (`name="value"`, with only
    447 `\\`, `\"` and `\xAB` as escapes -- CR and LF are written `\x0d`,
    448 `\x0a`).  They are installed to `$OUT/<fuzzer>.dict` and referenced from
    449 `$OUT/<fuzzer>.options`, which is how ClusterFuzz picks them up.
    450 
    451 They cover: HTTP methods and versions; framing headers
    452 (`Transfer-Encoding`, `Content-Length`, `chunked`, conflicting and
    453 malformed variants); chunk-size lines and chunk-extension syntax
    454 (`;ext`, `;ext=val`, `;ext="quoted"`, unterminated quotes) -- the exact
    455 grammar of commit `c13f4c64`; digest-auth parameters (`algorithm=`,
    456 `qop=`, `nonce=`, `realm=`, `userhash=`, `username*=`, `nc=`,
    457 `response=`) with the algorithm tokens `MD5`, `SHA-256`, `SHA-512-256`
    458 and their `-sess` variants plus deliberately unknown ones, and `auth` /
    459 `auth-int`; over-long hex `response=` values (commit `5a73c1ae`);
    460 percent-encoding, including truncated, invalid, double and overlong
    461 forms; base64; and the `multipart/form-data` and
    462 `application/x-www-form-urlencoded` vocabulary for the post processor.
    463 
    464 `fuzz_request.dict` additionally contains the literal `%%NONCE%%`
    465 placeholder, which the harness rewrites at send time into the most recent
    466 nonce the daemon issued.  Without it the digest `response=` code path is
    467 statistically unreachable (see `src/fuzz/README` section 2.3), so it is
    468 the single most valuable token in the file.
    469 
    470 The token list in `fuzz_common.h` (`fuzz_interesting_str`) is the
    471 generator's equivalent; the two are intentionally similar but not
    472 generated from each other.
    473 
    474 
    475 -------------------------------------------------------------------
    476 8. Known limitations
    477 -------------------------------------------------------------------
    478 
    479  *  **The request-body oracle is inactive under libFuzzer.**  The
    480     smuggling oracle of `fuzz_request` (see `src/fuzz/README` section
    481     2.4) compares what MHD delivers to the application against ground
    482     truth declared in an `op 1` segment of the input.  It is gated on
    483     `fuzz_pristine`, which the standalone driver sets for un-mutated
    484     inputs and which is never set when `FUZZ_NO_MAIN` is defined -- i.e.
    485     it is off for every OSS-Fuzz execution.  That is correct and
    486     intentional: libFuzzer mutates the request without mutating the
    487     declaration, so the ground truth would be wrong and every mutated
    488     input would look like a finding.  The consequence is that OSS-Fuzz
    489     catches memory-safety bugs, UB, panics and assertion failures, but
    490     *not* pure framing/desync defects of the `c13f4c64` kind.  Those stay
    491     the job of the in-tree driver (`make -C src/fuzz check`), which is
    492     another reason to keep running it in CI.
    493  *  **i386 runs ASan + libFuzzer only.**  That is an OSS-Fuzz
    494     restriction, not one of this build: locally the 32 bit targets build
    495     and run under every engine.  Worth knowing because the word size is
    496     exactly what makes some of these bugs interesting (`TESTING.md`
    497     section P3).
    498  *  **centipede is not claimed.**  It is a supported OSS-Fuzz engine but
    499     these targets have not been tried with it; unlike afl and honggfuzz,
    500     nobody has built it here.  Do not add it to `project.yaml` on the
    501     assumption that a plain `LLVMFuzzerTestOneInput()` target must work.
    502  *  **Nondeterminism.**  See the note about digest nonce timestamps in
    503     section 5.
    504  *  The `[libfuzzer]` section of the `.options` files is the only one
    505     used.  ClusterFuzz also understands other sections (for sanitizer
    506     options and, in some versions, environment variables), but nothing
    507     here depends on that, and the exact set of supported sections is not
    508     documented in the OSS-Fuzz repository -- if you ever need to pin
    509     `MHD_FUZZ_MIN_DISCIPLINE` or `MHD_FUZZ_MIN_MEM_LIMIT` for the hosted
    510     runs, verify the mechanism against the ClusterFuzz sources first
    511     rather than assuming it works.