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