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.