libmicrohttpd

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

README (48764B)


      1 GNU libmicrohttpd -- in-process fuzzing harnesses
      2 =================================================
      3 
      4 This directory contains eight in-process fuzzing harnesses for MHD.  All
      5 of them are *dual mode*:
      6 
      7   * they export the libFuzzer entry point
      8 
      9         int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size);
     10 
     11     so the very same source can be linked with clang/libFuzzer, AFL++ or
     12     OSS-Fuzz, and
     13 
     14   * they ship a **built-in standalone driver** (`fuzz_common.h`) with a
     15     deterministic, seeded generator + mutator loop, so they are useful
     16     with nothing but gcc and `-fsanitize=address,undefined`.
     17 
     18 The standalone driver is compiled unless `FUZZ_NO_MAIN` is defined.
     19 
     20 
     21 -------------------------------------------------------------------
     22 1. The harnesses
     23 -------------------------------------------------------------------
     24 
     25 fuzz_request.c          the flagship.  Feeds arbitrary bytes into a real
     26                         `struct MHD_Daemon` through a `socketpair()`,
     27                         using MHD_USE_NO_LISTEN_SOCKET +
     28                         MHD_add_connection() and external polling
     29                         (MHD_run()).  Everything runs in one thread, so
     30                         the harness is deterministic and fast (~20k
     31                         requests/s under ASAN+UBSAN).
     32 
     33 fuzz_str.c              direct fuzzing of the string primitives in
     34                         src/microhttpd/mhd_str.c.  Every output buffer is
     35                         malloc()ed at *exactly* the documented size so
     36                         that ASAN's redzone catches a one-byte overrun.
     37 
     38 fuzz_auth_header.c      direct fuzzing of the "Authorization:" header
     39                         parsers, MHD_get_rq_dauth_params_() and
     40                         MHD_get_rq_bauth_params_() (gen_auth.c), through
     41                         a minimal fabricated `struct MHD_Connection`.
     42                         ~250k execs/s.
     43 
     44 fuzz_postprocessor.c    fuzzing of MHD_post_process() with random
     45                         Content-Type (urlencoded / multipart with random
     46                         boundaries), random post-processor buffer sizes
     47                         and random chunking of the POST data.
     48 
     49 fuzz_options.c          the daemon *configuration* surface.  fuzz_request
     50                         always starts the daemon in one shape; this one
     51                         lets the input pick the MHD_FLAG bits and the
     52                         MHD_OPTION array, so the flag validation in
     53                         MHD_start_daemon(), parse_options_va(), the
     54                         internal polling thread, the thread pool,
     55                         epoll/poll/select, the listen socket, the per-IP
     56                         and per-daemon connection limits and quiesce all
     57                         become reachable.
     58 
     59 fuzz_eventloop.c        the external event loop and the scheduling of the
     60                         connection life cycle.  The input is not a
     61                         request but a *schedule*: a program of one-byte
     62                         opcodes interpreted against a live daemon, so the
     63                         application can poll at adversarial times, ignore
     64                         the timeout it was given, call
     65                         MHD_run_from_select() with descriptor sets that
     66                         do not match what MHD asked for, and
     67                         suspend/resume across those calls.  Byte 3 bit 4
     68                         also lets it stop the daemon with a connection
     69                         queued by MHD_add_connection() but never
     70                         started, which is the only way into
     71                         new_connection_close_() -- see section 5.5.
     72 
     73 fuzz_memorypool.c       direct fuzzing of src/microhttpd/memorypool.c,
     74                         the per-connection bump allocator.  A single
     75                         mis-computed offset there is a cross-request
     76                         information leak that ASAN cannot see on its own,
     77                         because the whole pool is one malloc()ed object;
     78                         the harness therefore carries its own oracles and
     79                         knows the red zone size that
     80                         MHD_ASAN_POISON_ACTIVE adds between two blocks.
     81 
     82 fuzz_tls.c              MHD's own TLS plumbing -- the MHD_USE_TLS option
     83                         surface (HTTPS_MEM_KEY/CERT/TRUST/DHPARAMS,
     84                         PRIORITIES, CRED_TYPE, KEY_PASSWORD, ALPN, SNI,
     85                         GNUTLS_PSK_CRED_HANDLER) rather than GnuTLS,
     86                         which has its own OSS-Fuzz project.  Byte 9 bit 7
     87                         switches on the TLS-PSK scenario, the only route
     88                         to psk_gnutls_adapter() -- see section 5.5.  It
     89                         needs a TLS backend and is therefore the one
     90                         harness contrib/oss-fuzz/build.sh does not build
     91                         (that build is --disable-https).
     92 
     93 Shared code lives in `fuzz_common.h` (header-only, so every harness
     94 stays a single translation unit).
     95 
     96 
     97 -------------------------------------------------------------------
     98 2. Design of fuzz_request
     99 -------------------------------------------------------------------
    100 
    101 2.1 Why a socketpair
    102 --------------------
    103 
    104 MHD_add_connection() accepts any already-connected socket, so a
    105 `socketpair(AF_UNIX, SOCK_STREAM)` is enough: no listen socket, no port,
    106 no TCP stack, no second thread.  The daemon is started with
    107 MHD_USE_NO_LISTEN_SOCKET and *without* MHD_USE_INTERNAL_POLLING_THREAD,
    108 and the harness pumps it with MHD_run() between sends.  A fake
    109 127.0.0.1 `struct sockaddr_in` is passed so that per-IP accounting and
    110 MHD_get_connection_info() see something sane.
    111 
    112 2.2 Input format
    113 ----------------
    114 
    115     byte 0   connection memory limit selector
    116              (index into {default,128,192,256,320,384,512,768,1024,
    117                           1400,1500,2048,4096,32768})
    118     byte 1   handler behaviour bitmask
    119                0x01  call MHD_digest_auth_check3() /
    120                      MHD_queue_auth_required_response3()
    121                0x02  call MHD_basic_auth_get_username_password3()
    122                0x04  run the request body through MHD_post_process()
    123                0x08  iterate MHD_get_connection_values() over headers,
    124                      GET arguments, cookies and footers
    125                0x10  unused (see 2.2.1)
    126                0x20  reply with a larger, copied response body
    127                      (only meaningful for response kind 0)
    128                0x40  reply 403 instead of 200
    129                0x80  unused
    130     byte 2   low nibble: MHD_OPTION_CLIENT_DISCIPLINE_LVL selector
    131              (index into {-3,-2,-1,0,1,2});
    132              high nibble: reserved for MHD_OPTION_SERVER_INSANITY
    133              (MHD 1.0.7 only defines MHD_DSC_SANE, so the value is 0)
    134     byte 3   digest configuration: bits 0-1 select the algorithm of the
    135              401 challenge {SHA-256, MD5, SHA-512-256, SHA-256},
    136              bit 2 selects the QOP, bits 4-5 select MHD_OPTION_NONCE_NC_SIZE
    137     byte 4-9 the API-selection block, see 2.2.1
    138     byte 10. a sequence of *send segments*.  Each segment starts with a
    139              little-endian 16 bit header:
    140 
    141                  (op << 14) | length          length <= 0x3FFF
    142 
    143              op 0   send `length` bytes on the current connection
    144              op 1   the payload is the *expected decoded request body*
    145                     (ground truth for the body oracle, see 2.4); it is
    146                     not sent
    147              op 2   send, then pump the daemon for extra rounds
    148              op 3   close the current connection, open a fresh one on
    149                     the same daemon, then send
    150 
    151 An explicit length encoding (rather than a magic delimiter) is used so
    152 that the fuzzer can move a split point without having to invent an
    153 escaping scheme.  Splitting matters: MHD's parser is incremental and
    154 several bugs only appear for particular split points.
    155 
    156 An input shorter than ten bytes is rejected: the configuration block is
    157 mandatory, and an input that short has no segment stream either.
    158 
    159 2.2.1 The API-selection block
    160 -----------------------------
    161 
    162 Bytes 4-9 pick which parts of the public API the iteration touches.
    163 They are always present.  Bytes 0-3 alone decide how MHD *parses* the
    164 request; these six decide which of the response constructors, which
    165 authentication entry point, which event loop, and which introspection
    166 calls run against the parsed result.
    167 
    168 The all-zero setting is the plainest one -- a static two byte buffer
    169 response, MHD_run() as the event loop, no suspend, no upgrade, no extra
    170 introspection -- so zeroing bytes 4-9 of any input reduces it to the
    171 request-parsing-only behaviour that the harness had before these bytes
    172 existed.
    173 
    174 These bytes were gated behind bit 0x10 of byte 1 while they were being
    175 brought up, so that the corpus predating them kept its byte-exact
    176 meaning.  The gate is gone; bit 0x10 is left unused rather than
    177 reassigned, so that a corpus file written while it was a gate cannot
    178 silently change meaning.  The reproducers in known-findings/ that predate
    179 the change (K1-K6, seven files) were migrated by inserting six zero bytes
    180 at offset 4 -- the identity transformation, since the all-zero block is
    181 the old behaviour -- and all seven still drive MHD along their recorded
    182 paths, verified by comparing the --verbose daemon, handler, body and
    183 challenge counts before and after.
    184 
    185     byte 4   response construction
    186                bits 0-3  which constructor to use:
    187                  0 buffer_static     1 buffer_copy      2 empty
    188                  3 buffer/PERSISTENT 4 buffer/MUST_FREE 5 buffer/MUST_COPY
    189                  6 buffer_with_free_callback            7 data (copy)
    190                  8 data (free)       9 callback, known length
    191                 10 callback, MHD_SIZE_UNKNOWN (chunked reply)
    192                 11 fd               12 fd_at_offset    13 fd64
    193                 14 pipe             15 iovec
    194                bits 4-5  number of response headers to add (0-3)
    195                bit  6    also add a response footer (a chunked trailer)
    196                bit  7    exercise MHD_get_response_header(),
    197                          MHD_get_response_headers(),
    198                          MHD_del_response_header() and
    199                          MHD_set_response_options()
    200     byte 5   authentication entry point
    201                bits 0-3  0 check3 (as before)     1 check
    202                          2 check2                 3 check_digest
    203                          4 check_digest2          5 check_digest3
    204                          6 get_username           7 get_username3
    205                          8 get_request_info3      9 as 0
    206                bits 4-5  challenge variant: 0 queue_auth_required_response3,
    207                          1 queue_auth_fail_response,
    208                          2 queue_auth_fail_response2,
    209                          3 the basic-auth pair
    210                bit  6    use the v1 MHD_basic_auth_get_username_password()
    211                bit  7    also call the connection-less digest helpers
    212     byte 6   event loop and introspection
    213                bits 0-1  0 MHD_run(), 1 MHD_get_fdset()+run_from_select(),
    214                          2 and 3 the *2 variants
    215                bit  2    query all four MHD_get_timeout*() forms
    216                bit  3    MHD_lookup_connection_value(),
    217                          MHD_lookup_connection_value_n(),
    218                          MHD_get_connection_URI_path_n()
    219                bit  4    MHD_set_connection_value()
    220                bit  5    MHD_get_connection_info() / MHD_get_daemon_info()
    221                bit  6    MHD_set_connection_option()
    222                bit  7    MHD_quiesce_daemon() before stopping
    223     byte 7   suspend and upgrade
    224                bits 0-1  0 none, 1 suspend+resume in the handler,
    225                          2 suspend and resume from the pump loop,
    226                          3 as 1
    227                bit  2    allow HTTP "Upgrade" (only acted on when the
    228                          request really asks for one, see below)
    229                bits 3-4  which MHD_upgrade_action() to issue first
    230     byte 8   seed picking the response header names and values, and the
    231              MHD_RF_* flags for MHD_set_response_options()
    232     byte 9   seed for the content-reader callback: body length, block
    233              size, and whether it fails part way through
    234 
    235 Two constraints on the harness are worth stating, because both are
    236 application-contract requirements rather than things worth fuzzing, and
    237 violating either makes MHD abort on input that is perfectly legal:
    238 
    239   * the connection is only suspended when the handler is about to return
    240     MHD_YES.  Returning MHD_NO asks MHD to terminate the connection, and
    241     terminating one that the same callback just suspended trips
    242     mhd_assert (! connection->suspended) in MHD_connection_close_();
    243 
    244   * a 101 response is only queued for a request that is actually an
    245     upgrade request (HTTP/1.1, an "Upgrade" header, and a "Connection"
    246     header naming the upgrade token and not "close").  All three come
    247     off the wire, so the path stays attacker-driven.
    248 
    249     This is exactly the check a real application can perform, and no
    250     more.  It deliberately does not try to predict whether MHD will
    251     accept the upgrade: a request that passes it used to abort MHD, which
    252     is finding K7 in section 6, and MHD_queue_response() now answers
    253     MHD_NO for that case instead.  Do not tighten the condition to keep
    254     the suite green -- an application cannot do better than this, so
    255     neither should the harness.
    256 
    257 The message-framing headers (Content-Length, Transfer-Encoding) are
    258 deliberately absent from the response-header table: MHD generates them
    259 itself from the response object, so an application that also sets them
    260 by hand is lying to the library about its own body.
    261 MHD_RF_INSANITY_HEADER_CONTENT_LENGTH is the sanctioned way to explore
    262 that corner and is reachable through bit 7 of byte 4.
    263 
    264 2.3 The %%NONCE%% placeholder
    265 -----------------------------
    266 
    267 Interesting parts of digestauth.c are only reached *after* the client
    268 presents a nonce that MHD itself generated.  A stateless fuzzer can
    269 never guess one.  Therefore the harness rewrites the literal ASCII token
    270 
    271     %%NONCE%%
    272 
    273 inside a segment, at send time, into the most recent `nonce="..."` value
    274 seen in a response from the daemon.  A generated (or hand-written) input
    275 can thus be:
    276 
    277     request 1:  GET /a  ->  handler calls MHD_digest_auth_check3(),
    278                             gets MHD_DAUTH_WRONG_HEADER and replies
    279                             401 + WWW-Authenticate: Digest ... nonce="X"
    280     op 3:       new connection
    281     request 2:  GET /a with Authorization: Digest ... nonce="%%NONCE%%"
    282 
    283 which walks all the way into the 'response' comparison.  Without this
    284 the over-long `response=` stack overflow (see 5.4) is unreachable.
    285 
    286 2.4 Oracles
    287 -----------
    288 
    289 Memory errors are caught by ASAN/UBSAN and aborts by the signal
    290 handlers.  In addition fuzz_request installs two behavioural oracles:
    291 
    292   a) MHD_set_panic_func() -- any MHD_PANIC() reached from network input
    293      is a finding (a remote abort), not a legitimate "API violation".
    294 
    295   b) A request-body oracle.  Framing bugs (chunked transfer coding,
    296      Content-Length) do not corrupt memory, they corrupt *data*, which
    297      is exactly what HTTP request smuggling exploits.  The input can
    298      therefore declare the expected decoded body in an `op 1` segment.
    299      Every byte that MHD hands to the application must be the next
    300      expected byte, and when MHD completes the request the delivered
    301      body must be complete.  MHD is free to reject the request at any
    302      point -- only what it *does* deliver is checked.
    303 
    304      The declaration is honoured only for un-mutated inputs (the driver
    305      exposes this as `fuzz_pristine`), because a random mutation would
    306      of course invalidate the ground truth.
    307 
    308 2.5 The generator
    309 -----------------
    310 
    311 Purely random bytes essentially never form a valid HTTP request, so the
    312 standalone driver uses a small HTTP grammar (`fuzz_generate()`), and
    313 then optionally applies byte-level mutations on top.  Shapes:
    314 
    315     0 SHAPE_PLAIN            random method/target/version + headers
    316     1 SHAPE_NOHDR_QARG       *no header lines at all* plus a trailing
    317                              query argument without '=' (this is the
    318                              exact shape needed for the read-buffer
    319                              shift-back bug; the generator also forces a
    320                              small connection memory pool for it)
    321     2 SHAPE_CL_BODY          Content-Length body + body oracle
    322     3 SHAPE_CHUNKED          chunked body with chunk extensions
    323                              (";ext", ";ext=val", ";ext=\"quoted\"",
    324                              ";a=1;b=2;c") + trailers + body oracle
    325     4 SHAPE_DIGEST_SIMPLE    Authorization: Digest with a randomised
    326                              parameter set, including unknown
    327                              `algorithm=` tokens and `response=` values
    328                              of every length up to 128 hex digits
    329     5 SHAPE_DIGEST_REPLAY    the two-request nonce handshake of 2.3
    330     6 SHAPE_BASIC            Authorization: Basic with random base64
    331     7 SHAPE_POST_FORM        urlencoded / multipart POST bodies
    332     8 SHAPE_WEIRD            folded headers, bare CR, bare LF,
    333                              whitespace before the colon, percent
    334                              encoding, absolute-form targets, ...
    335 
    336 `MHD_FUZZ_SHAPE=<n>` restricts the generator to a single shape, which is
    337 very handy for triage and for regression-testing a specific past bug.
    338 
    339 
    340 -------------------------------------------------------------------
    341 3. Running the harnesses
    342 -------------------------------------------------------------------
    343 
    344 Build (gcc only, no clang required):
    345 
    346     SRC=/path/to/libmicrohttpd            # configured build tree
    347     gcc -g -O1 -Wall -Wextra \
    348         -fsanitize=address,undefined -fno-sanitize-recover=all \
    349         -I$SRC -I$SRC/src/include -I$SRC/src/microhttpd -I$SRC/src/fuzz \
    350         -o fuzz_request $SRC/src/fuzz/fuzz_request.c \
    351         $SRC/src/microhttpd/.libs/libmicrohttpd.a -lpthread
    352 
    353 (the same command line for fuzz_str, fuzz_auth_header and
    354 fuzz_postprocessor; the static archive is required because fuzz_str and
    355 fuzz_auth_header use functions that are hidden in the shared object).
    356 
    357 Options of the built-in driver (identical for all harnesses):
    358 
    359     --iterations=N     number of generate/mutate iterations   [3000]
    360     --seed=N           PRNG seed; (harness, seed) fully determines a run
    361     --corpus-dir=DIR   replay every regular file in DIR and exit
    362     --file=PATH        replay a single input and exit  (crash repro)
    363     --crash-dir=DIR    where reproducers are written           [crashes]
    364     --timeout=SEC      per-iteration watchdog, 0 disables      [20]
    365     --write-corpus=DIR dump the built-in seed corpus to DIR
    366     --skip-seeds       do not replay the built-in corpus first
    367     --verbose          enable MHD's error log + print statistics
    368     --help
    369 
    370 Environment variables (all optional):
    371 
    372     MHD_FUZZ_ITERATIONS, MHD_FUZZ_SEED, MHD_FUZZ_TIMEOUT,
    373     MHD_FUZZ_CRASH_DIR, MHD_FUZZ_VERBOSE, MHD_FUZZ_SKIP_SEEDS
    374 
    375     MHD_FUZZ_SHAPE=<n>              (fuzz_request) restrict the generator
    376                                     to one grammar shape
    377     MHD_FUZZ_MIN_DISCIPLINE=<n>     (fuzz_request) lower bound for
    378                                     MHD_OPTION_CLIENT_DISCIPLINE_LVL,
    379                                     default -3 (the full range)
    380     MHD_FUZZ_MIN_MEM_LIMIT=<n>      (fuzz_request) lower bound for
    381                                     MHD_OPTION_CONNECTION_MEMORY_LIMIT,
    382                                     default 0 (the full range)
    383     MHD_FUZZ_MODEL_DIGEST_SINK=1    (fuzz_str) enable the modelled
    384                                     digest 'response' call site, see 5.4
    385 
    386 Typical use:
    387 
    388     # quick smoke test (a couple of seconds)
    389     ./fuzz_request
    390 
    391     # a real session
    392     ./fuzz_request --iterations=5000000 --seed=$RANDOM
    393 
    394     # regression: replay the whole checked-in corpus
    395     ./fuzz_request --corpus-dir=corpus
    396     ./fuzz_str --corpus-dir=corpus       # ignores foreign files gracefully
    397 
    398     # reproduce a crash
    399     ./fuzz_request --file=crashes/crash-fuzz_request-seed3-iter55.bin
    400 
    401 
    402 -------------------------------------------------------------------
    403 4. Reproducing a failure
    404 -------------------------------------------------------------------
    405 
    406 Whenever the process dies -- ASAN error, UBSAN error, `mhd_assert()`,
    407 MHD_PANIC(), a body-oracle finding, or the watchdog -- the input of the
    408 running iteration is written to
    409 
    410     $MHD_FUZZ_CRASH_DIR/crash-<harness>-seed<S>-iter<N>.bin
    411 
    412 and a line is printed telling you the harness, the seed and the
    413 iteration.  The dump is produced from
    414 
    415   * `__sanitizer_set_death_callback()` (weakly linked; present whenever
    416     the binary is built with ASAN), and
    417   * SIGABRT/SIGSEGV/SIGBUS/SIGILL/SIGFPE/SIGALRM handlers,
    418 
    419 using only async-signal-safe calls.  Replay with `--file=...`; the run
    420 is fully deterministic, so `--seed=S --iterations=N+1` reproduces the
    421 whole session as well.
    422 
    423 
    424 -------------------------------------------------------------------
    425 5. What these harnesses find (regression coverage)
    426 -------------------------------------------------------------------
    427 
    428 The four vulnerabilities fixed in MHD 1.0.7+1 are all rediscovered from
    429 scratch.  Each has a dedicated seed in `corpus/`, and the generator
    430 finds each of them on its own within a few thousand iterations.
    431 
    432 5.1 digestauth.c: unknown `algorithm=` token -> MHD_PANIC()
    433     An `algorithm=` token MHD does not know parses to
    434     MHD_DIGEST_AUTH_ALGO3_INVALID, which is 0, so the allow-mask test
    435     `c_algo == (c_algo & malgo3)` passes for *any* mask; the code then
    436     calls digest_init_one_time() with an invalid algorithm and panics.
    437     Found by: SHAPE_DIGEST_SIMPLE / SHAPE_DIGEST_REPLAY, the panic hook,
    438     corpus seed `digest-unknown-algorithm`.
    439 
    440 5.2 connection.c get_req_headers(): read-buffer shift-back underflow
    441     Needs, all at once: a small MHD_OPTION_CONNECTION_MEMORY_LIMIT
    442     (MHD_BUF_INC_SIZE (1500) > read_buffer_size), *no header lines*, and
    443     a trailing query argument without '=' (whose `value` is NULL).
    444     Found by: SHAPE_NOHDR_QARG, corpus seeds
    445     `small-pool-trailing-query-arg[-2]`.
    446 
    447 5.3 connection.c process_request_body(): chunk-extension CRLF
    448     `chunk_size_line_len = i` instead of `i + 2` leaves the CRLF of the
    449     chunk-size line in the stream, so the following chunk data is
    450     shifted -- a body desync, i.e. a request-smuggling primitive.  This
    451     corrupts no memory, so it is caught by the body oracle (2.4).
    452     Found by: SHAPE_CHUNKED, corpus seeds `chunked-with-extensions`,
    453     `chunked-split`.
    454 
    455 5.4 digestauth.c: over-long `response=` -> stack buffer overflow
    456     `response` was accepted up to `digest_size * 4` characters (128 for
    457     SHA-256) and then decoded with MHD_hex_to_bin() into
    458     `uint8_t hash1_bin[MAX_DIGEST]` (32 bytes) -- up to 64 bytes
    459     written, 32 bytes of stack smashed.  Reaching it requires a *valid*
    460     nonce, hence the %%NONCE%% mechanism of 2.3.
    461     Found by: SHAPE_DIGEST_REPLAY, corpus seed
    462     `digest-overlong-response`.
    463 
    464     fuzz_str additionally reproduces the underlying primitive:
    465     MHD_hex_to_bin() has no output-size parameter and writes len/2
    466     bytes, so any caller with a fixed-size buffer must bound the input
    467     length itself.  `MHD_FUZZ_MODEL_DIGEST_SINK=1` enables a target that
    468     replays exactly the pre-fix call site (32 byte heap buffer, input
    469     length bounded only by 4 * 32) and ASAN reports the overflow
    470     immediately.  The target models a *caller*, not the library, so it
    471     is off by default.
    472 
    473 5.5 Code that only a deliberate scenario reaches
    474     A coverage-guided engine gets to a branch by mutating towards it, so
    475     it never gets to one that needs several unrelated configuration
    476     bytes to be right at the same time: the intermediate inputs score no
    477     better than the ones around them, and the fuzzer has no gradient to
    478     follow.  An 8 hour, 12 core, 1.6 billion execution campaign, on top
    479     of the whole test suite, left exactly three library functions at zero
    480     coverage for that reason.  All three now have a scenario:
    481 
    482     psk_gnutls_adapter() (daemon.c) -- MHD's only piece of TLS code that
    483     takes a buffer straight from an application callback.  Needs
    484     GNUTLS_CRD_PSK, a PSK-capable priority string *on both ends*
    485     ("NORMAL" has none), MHD_OPTION_GNUTLS_PSK_CRED_HANDLER, and a
    486     client offering a PSK identity.  fuzz_tls byte 9 bit 7 switches all
    487     four on together; bits 3-5 then pick which arm of the adapter to
    488     exercise (missing callback, failing callback, 4 KiB key, key length
    489     taken from the identity the client sent, size above UINT_MAX, and
    490     the two sizes below the MHD_PSK_MIN_SIZE the adapter enforces --
    491     zero and one byte short, which together with the exactly-minimum
    492     PSK_OK bracket that check from both sides).  Seeds `psk-*`.
    493 
    494     new_connection_close_() (daemon.c) -- frees a connection that was
    495     accepted but never started.  On a thread-safe daemon (the default,
    496     including a pure external event loop) MHD_add_connection() only
    497     queues the socket; the connection object is built by the next run.
    498     The function is reachable only if the daemon is stopped before that
    499     run happens, and every harness used to call MHD_run() once more
    500     during teardown.  fuzz_eventloop byte 3 bit 4 queues one connection
    501     after the teardown and stops without running.  Seeds
    502     `stop-with-queued-connection`, `stop-with-queued-after-quiesce`.
    503     It is not a race: no second thread is involved.
    504 
    505     MHD_check_response_header_token_ci() (response.c) -- this one was
    506     not a fuzzing problem.  The function was added in 2017 and never
    507     called, so no input could reach it.  The single place that wants it,
    508     the "Connection: upgrade" check in MHD_queue_response(), open-coded
    509     the scan against response->first_header instead; that now calls the
    510     helper, and the existing `ext-upgrade` seed and K7 cover it.
    511 
    512 
    513 -------------------------------------------------------------------
    514 6. Findings against MHD 1.0.7 - all fixed, kept as regressions
    515 -------------------------------------------------------------------
    516 
    517 Running these harnesses against v1.0.7 built with `--enable-asserts`
    518 reported the following *additional* issues on top of the four
    519 vulnerabilities of section 5.  All of them were `mhd_assert()`s reachable
    520 from network input, i.e. a remote abort in builds that keep assertions
    521 enabled, and all of them are fixed on master.  They are documented here
    522 because the reproducers are kept as a regression corpus: a failure of one
    523 of them means the corresponding fix has been undone.  Byte-exact reproducers are in `corpus/known-findings/`; replay
    524 one with
    525 
    526     ./fuzz_request --file=corpus/known-findings/K1-digest-empty-realm.bin
    527 
    528 K1  digestauth.c:2467  is_param_equal():
    529         mhd_assert (0 != param->value.len)                 -> fixed in 300a2ab0
    530     Trigger (default daemon configuration!), one request:
    531         GET /a HTTP/1.1
    532         Host: x
    533         Authorization: Digest username="user", realm="", nonce="0000",
    534                        uri="/a", response="00"
    535     digest_auth_check_all_inner() rejects a *missing* realm/username but
    536     not an *empty* one, so a zero-length parameter reaches
    537     is_param_equal(), whose documented precondition is a non-empty
    538     value.  Requires only that the application calls
    539     MHD_digest_auth_check3().  Real defect (missing validation).
    540     Repro: corpus/known-findings/K1-digest-empty-realm.bin
    541 
    542 K2  connection.c:3582  handle_recv_no_space():
    543         mhd_assert ((MHD_PROC_RECV_BODY_CHUNKED != stage) ||
    544                     ! c->rq.some_payload_processed)        -> fixed in 68c83f22
    545     Trigger: small MHD_OPTION_CONNECTION_MEMORY_LIMIT (<= ~400 bytes)
    546     plus a chunked body whose *second* chunk-size line carries a chunk
    547     extension that does not fit into the remaining read buffer.  The
    548     flag reflects the last application callback only and survives later
    549     reads, so the assertion is over-strong; the code below it already
    550     handles the situation.  Stale assertion.
    551     Repro: corpus/known-findings/K2-chunkext-no-space.bin
    552 
    553 K3  connection.c:2881  transmit_error_response_len():
    554         mhd_assert (! connection->stop_with_error)         -> fixed in e04eb218
    555     Trigger: small connection memory pool plus an over-long, unterminated
    556     chunk extension:
    557         POST /a HTTP/1.1 / Transfer-Encoding: chunked
    558         d;ext="qqqqqqqq...        (longer than the read buffer)
    559     handle_req_chunk_size_line_no_space() is missing a `return` after it
    560     has already queued the "chunk extension too big" response.  Real
    561     defect: in a release build the second call forces the connection to
    562     MHD_CONNECTION_CLOSED and the 413 response is never sent.
    563     Repro: corpus/known-findings/K3-chunkext-stop-with-error.bin
    564 
    565 K4  connection.c:6099  get_req_header():
    566         mhd_assert ((0 == c->rq.hdrs.hdr.value_start) ||
    567                     (0 != c->rq.hdrs.hdr.name_len))        -> fixed in 0b750975
    568     Trigger a) MHD_OPTION_CLIENT_DISCIPLINE_LVL <= -1, first header line
    569     starting with whitespace:
    570         GET /a HTTP/1.1\r\n Host: x\r\n\r\n
    571     Trigger b) MHD_OPTION_CLIENT_DISCIPLINE_LVL <= -2, empty header
    572     name:
    573         GET /a HTTP/1.1\r\n: value\r\nHost: x\r\n\r\n
    574     Both shapes are explicitly allowed by those discipline levels.
    575     Stale assertion.
    576     Repro: corpus/known-findings/K4a-wsp-first-header.bin,
    577            corpus/known-findings/K4b-empty-header-name.bin
    578 
    579 K5  connection.c:6394  get_req_header():
    580         mhd_assert ('\r' != chr)                           -> fixed in 6fcdfd43
    581     Trigger: MHD_OPTION_CLIENT_DISCIPLINE_LVL = -3, which sets
    582     `bare_cr_keep = true`; the branch that keeps a bare CR falls through
    583     into the "not a whitespace, not the end of the line" arm whose
    584     assertion predates that mode.
    585         GET /a HTTP/1.1\r\nHost: x\r\nX: y\r\r\n\r\n
    586     Stale assertion.
    587     Repro: corpus/known-findings/K5-bare-cr-keep.bin
    588 
    589 K6  digestauth.c:860  check_nonce_nc():
    590         mhd_assert (0 == nn->nonce[noncelen])
    591     The nonce-nc slot array is indexed by a hash of the nonce, but the
    592     slot content is compared assuming the *stored* nonce has the same
    593     length as the presented one.  A client can therefore make MHD read
    594     the terminator of a nonce at the wrong offset by presenting a nonce
    595     whose length belongs to a different digest algorithm.
    596     Note that this one is *timing dependent*: the nonce carries a
    597     millisecond timestamp and whether it counts as stale depends on the
    598     wall clock, so the same input reproduces only in a fraction of the
    599     replays (about 1 in 30 for the corpus file below).  Replay it in a
    600     loop.
    601     Trigger (MHD_OPTION_NONCE_NC_SIZE = 1 makes every nonce land in slot
    602     0, which turns the collision into a certainty; larger arrays only
    603     need more attempts):
    604         request 1: GET /a          -> 401 with a SHA-256 nonce
    605                                       (76 chars) stored in the slot
    606         request 2: Authorization: Digest username="user",
    607                    realm="TestRealm", nonce="<44 zeros>", uri="/a",
    608                    response="e"
    609                    (no algorithm parameter -> MD5 -> nonce length 44,
    610                     all-zero timestamp -> not stale)
    611     The generator needs ~150k iterations to hit it on its own.
    612     Repro: corpus/known-findings/K6-nonce-length-collision.bin
    613 
    614 K7  connection.c:2596  build_header_response():
    615         mhd_assert ((NULL == r->upgrade_handler) ||
    616                     (MHD_CONN_MUST_UPGRADE == c->keepalive))
    617     Fixed by commit acef58a0.  Was driven entirely from the wire, with
    618     default daemon options.
    619 
    620     While parsing the request headers, connection.c sets
    621         c->keepalive = MHD_CONN_MUST_CLOSE
    622     in three places for requests whose message framing it distrusts:
    623 
    624       4988  two "Content-Length" headers with different values
    625             (client discipline -3 only)
    626       5012  "Transfer-Encoding" on an HTTP/1.0 request
    627             (client discipline <= 0)
    628       5051  "Content-Length" *and* "Transfer-Encoding: chunked" in the
    629             same request -- no discipline gate at all, so this one
    630             fires with the default configuration
    631 
    632     In every case the request is then handed to the access handler as a
    633     normal request.  If the handler answers 101 with a response from
    634     MHD_create_response_for_upgrade(), MHD_queue_response() accepts it:
    635     it checks MHD_ALLOW_UPGRADE, the status code, the "Connection"
    636     header and the HTTP version, but not whether the connection can
    637     still be kept open.  keepalive_possible() then returns
    638     MHD_CONN_MUST_CLOSE from its very first test, which is placed
    639     *before* the upgrade branch, and the assertion above fires while the
    640     reply header is being built.
    641 
    642     The application cannot defend itself.  The harness checks the
    643     request the way an application would -- HTTP/1.1, an "Upgrade"
    644     header, and a "Connection" header naming the upgrade token and not
    645     "close" -- and the reproducer satisfies all three.  MHD's decision
    646     is not exposed through any public accessor.
    647 
    648     It needs --enable-asserts and an application that answers upgrade
    649     requests, so it is a robustness defect rather than a memory-safety
    650     one; but it is reachable from a single well-formed request against a
    651     default-configured server, which is a good deal worse than the
    652     "application misuse" it first looked like.
    653 
    654     The fix rejects the response in MHD_queue_response()
    655     (connection.c:8338) next to the other upgrade preconditions, so the
    656     application gets MHD_NO -- which it already has to handle -- instead
    657     of an abort.
    658     Repro: corpus/known-findings/K7-upgrade-after-must-close.bin
    659         GET / HTTP/1.1
    660         Host: x
    661         Connection: Upgrade
    662         Upgrade: fuzz-protocol
    663         Content-Length: 0
    664         Transfer-Encoding: chunked
    665         (then "0\r\n\r\n")
    666 
    667 K8  digestauth.c  MHD_digest_auth_check_digest2():
    668         MHD_PANIC ("Wrong 'malgo3' value, only one base hashing
    669                     algorithm ... must be specified, API violation")
    670     Fixed by commit 07c051dd.  An API-contract defect rather than a
    671     parsing bug: unlike K7 the offending argument came from the
    672     application, not from the network.
    673 
    674     MHD_DIGEST_ALG_AUTO is a documented member of
    675     enum MHD_DigestAuthAlgorithm, and the `algo` parameter of
    676     MHD_digest_auth_check_digest2() is documented only as "digest
    677     algorithms allowed for verification".  But the function maps AUTO to
    678     MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION and forwards it to
    679     MHD_digest_auth_check_digest3(), which requires *exactly one* base
    680     hashing algorithm because the caller supplies a pre-computed digest
    681     of one specific length -- and aborts through MHD_PANIC() when more
    682     than one is named.
    683 
    684     So MHD_digest_auth_check_digest2 (..., MHD_DIGEST_ALG_AUTO)
    685     deterministically kills the process.  The same is not true of
    686     MHD_digest_auth_check2(), which takes a password rather than a
    687     digest and whose AUTO handling is fine.
    688 
    689     Unlike K7 this is entirely under the application's control -- no
    690     network input is involved -- so it is a usability trap rather than a
    691     robustness bug: AUTO simply cannot be used with the
    692     pre-computed-digest entry points, and no mapping can rescue it,
    693     because the digest length does not identify the algorithm (SHA-256
    694     and SHA-512/256 digests are both 32 bytes).
    695 
    696     The fix documents the restriction in microhttpd.h and returns MHD_NO
    697     instead of aborting.  AUTO is left unusable with the
    698     pre-computed-digest entry points, deliberately: there is nothing to
    699     map it to.
    700     Repro: none committed.  The argument is the application's, so the
    701     harness never passes AUTO there (see run_digest_check() in
    702     fuzz_request.c); to see it, change the MHD_DIGEST_ALG_* argument of
    703     the variant-4 call to MHD_DIGEST_ALG_AUTO and replay
    704     corpus/fuzz_request-37.bin.
    705 
    706 K9  postprocessor.c:1119  post_process_multipart():
    707         LeakSanitizer: direct leak, strdup() from MHD_post_process()
    708     Found by fuzz_postprocessor, not fuzz_request -- the first finding
    709     that did not come from the daemon harness.  Remotely triggerable,
    710     default configuration, no assertions needed.
    711 
    712     On entering PP_PerformCheckMultipart the code did
    713 
    714         pp->nested_boundary = strstr (pp->content_type, "boundary=");
    715         ...
    716         pp->nested_boundary = strdup (&pp->nested_boundary[9]);
    717 
    718     The first assignment stores an *interior pointer into
    719     pp->content_type* over whatever pp->nested_boundary held.  If the
    720     post processor already owned a boundary -- which it does for every
    721     nested "multipart/mixed" part after the first, unless the state
    722     machine happened to pass through PP_PerformCleanup in between -- that
    723     allocation is lost.  MHD_destroy_post_processor() frees only the last
    724     one.
    725 
    726     So a body with N nested multipart/mixed parts, each carrying its own
    727     "boundary=", leaks N-1 blocks, and the client picks how long each one
    728     is.  That makes it a memory-exhaustion vector against any application
    729     that calls MHD_post_process() on multipart input, not the one byte
    730     the reproducer happens to leak (its boundary is the empty string).
    731 
    732     Fixed by copying into a local first and releasing any previously
    733     owned boundary before taking ownership of the new one; the error
    734     path no longer overwrites the old pointer either.
    735     Repro: corpus/known-findings/K9-fuzz_postprocessor-nested-boundary-leak.bin
    736 
    737 K10 memorypool.c  MHD_pool_deallocate():  end block of an exactly-full
    738     pool was never returned; the front/end dispatch tested pool->pos,
    739     where the two ranges meet.  Fixed.
    740 
    741 K11 memorypool.c  MHD_pool_reallocate():  returned non-NULL for a
    742     wrapping @a new_size, handing the caller a block it believed was
    743     nearly SIZE_MAX bytes long.  Fixed.
    744 
    745 K12 daemon.c  MHD_start_daemon_va():  leaked the GnuTLS DH parameters
    746     built from MHD_OPTION_HTTPS_MEM_DHPARAMS on every startup-failure
    747     exit.  The application cannot free them.  Fixed.
    748 
    749 K13 daemon.c  MHD_epoll() / MHD_quiesce_daemon():  both threads remove
    750     the listening socket from the epoll set, and the loser aborts on
    751     ENOENT.  Open; two alternative fixes are proposed as
    752     ../../patches/K13.diff (tolerate ENOENT everywhere) and
    753     ../../patches/K13b.diff (take a lock, keep the strict check).
    754 
    755 K14 daemon.c:1358  call_handlers():
    756         mhd_assert (! force_close || MHD_CONNECTION_CLOSED == con->state)
    757     Open, fix proposed in ../../patches/K14.diff.
    758 
    759     MHD_connection_handle_read() closes the connection when its
    760     @a socket_error argument is set -- but only once it gets far enough
    761     to try.  It returns early, leaving the state untouched, when the
    762     connection is suspended, when a TLS handshake is in progress, and
    763     when the read buffer has no free space at all.  The caller asserts
    764     the post-condition unconditionally.
    765 
    766     Instrumenting the assertion site with the reproducer gives
    767 
    768         state=12 (HEADERS_PROCESSED)  suspended=0
    769         rb_size=2021  rb_off=2021  evinfo=READ
    770 
    771     i.e. the read buffer is exactly full.  A client that fills the
    772     connection memory pool without the application consuming the body
    773     gets there; no unusual API use is involved.  Observed through
    774     MHD_run_from_select2(), i.e. from the external event loop.
    775 
    776     The reproducer does NOT fire on a plain in-tree gcc build: ASan's
    777     pool redzones change the buffer arithmetic enough to matter, so
    778     confirm it against the OSS-Fuzz build configuration.
    779     Repro: corpus/known-findings/K14-fuzz_eventloop-force-close-not-closed.bin
    780 
    781 K15 daemon.c:9201  close_all_connections():
    782         mhd_assert (MHD_D_IS_USING_THREADS_ (daemon))
    783     Fixed; found while giving new_connection_close_() its first coverage
    784     (section 5.5).
    785 
    786     Three places describe the same invariant and one of them disagreed.
    787     internal_add_connection() queues a connection when
    788     `external_add && MHD_D_IS_THREAD_SAFE_(daemon)`, and
    789     new_connections_list_process_() asserts MHD_D_IS_THREAD_SAFE_ --
    790     that is, whenever the daemon was not started with
    791     MHD_USE_NO_THREAD_SAFETY, which includes every external event loop.
    792     The shutdown drain asserted the much stronger
    793     MHD_D_IS_USING_THREADS_ instead, i.e. that the daemon has an
    794     internal polling thread.
    795 
    796     So an application that drives an external event loop, calls
    797     MHD_add_connection() and then MHD_stop_daemon() before the next
    798     MHD_run() aborts on an --enable-asserts build.  No threads and no
    799     race are involved, and a build with NDEBUG handles the same sequence
    800     correctly, which is why nothing had noticed: the list is exactly what
    801     holds the connection at that point and new_connection_close_() frees
    802     it properly.
    803 
    804     The assert was the outlier and now matches the other two sites.  The
    805     opposite reading -- that an external event loop should not queue at
    806     all and internal_add_connection() should test
    807     MHD_D_IS_USING_THREADS_ -- would change MHD_add_connection()'s
    808     threading contract, since queueing is what makes that call safe from
    809     another thread on a daemon with no thread of its own.
    810     Repro: corpus/known-findings/K15-fuzz_eventloop-stop-with-queued-connection.bin
    811 
    812 Status
    813 ------
    814 
    815 K1-K12 and K15 are fixed on master:
    816 
    817     K1  300a2ab0     K4a 0b750975     K7  acef58a0     K10 (memorypool)
    818     K2  68c83f22     K4b 0b750975     K8  07c051dd     K11 (memorypool)
    819     K3  e04eb218     K5  6fcdfd43     K9  (postproc)   K12 (daemon/TLS)
    820                      K6  f438804c                      K15 (daemon)
    821 
    822 K13 and K14 are open, with proposed fixes in ../../patches/.  Until K14
    823 lands, `make check-corpus` fails on an `--enable-asserts` build, because
    824 its reproducer is committed and still reproduces -- which is the
    825 documented behaviour for an open finding, but worth knowing before a CI
    826 run.
    827 
    828 The reproducers in `corpus/known-findings/` otherwise all replay clean
    829 on a build configured with `--enable-asserts`, and
    830 `make check-corpus` asserts exactly that -- it replays that directory
    831 along with the generated corpus.  K8 has no reproducer, because its
    832 trigger is an argument the application chooses rather than anything that
    833 comes off the wire.
    834 
    835 A reproducer belongs to the harness that found it, and says so in its
    836 name: `K<n>-<harness>-<what>.bin`.  The ones without a harness in the
    837 name predate the convention and are all fuzz_request inputs.
    838 `contrib/oss-fuzz/make_seed_corpus.sh` routes each one into the right
    839 target's seed corpus on that basis -- a fuzz_postprocessor reproducer in
    840 fuzz_request's corpus would just be an uninteresting input.
    841 
    842 When the next finding is opened, its reproducer goes into
    843 `corpus/known-findings/` and will make `make check-corpus` fail until the
    844 fix lands.  That is the intended behaviour: it is the same arrangement as
    845 the `XFAIL_TESTS` entries in `src/microhttpd/Makefile.am`, except that
    846 here the expectation is not encoded, so the commit that adds a live
    847 reproducer should say so.  `contrib/oss-fuzz/make_seed_corpus.sh` has to
    848 skip such a reproducer as well, or every ClusterFuzz run starts by
    849 rediscovering it; see the comment there.
    850 
    851 The `make check` defaults in Makefile.am
    852 
    853     MHD_FUZZ_ITERATIONS     = 50000
    854     MHD_FUZZ_MIN_DISCIPLINE = -3      (full range)
    855     MHD_FUZZ_MIN_MEM_LIMIT  = 0       (full range)
    856 
    857 exercise the whole matrix and take about three seconds in total under
    858 ASAN+UBSAN.  A real session:
    859 
    860     MHD_FUZZ_MIN_DISCIPLINE=-3 MHD_FUZZ_MIN_MEM_LIMIT=0 \
    861       ./fuzz_request --iterations=1000000 --seed=1
    862 
    863 -------------------------------------------------------------------
    864 7. Building with clang/libFuzzer or AFL++
    865 -------------------------------------------------------------------
    866 
    867 Both need `-DFUZZ_NO_MAIN` so that the driver's `main()` is left out.
    868 
    869 7.1 libFuzzer
    870 -------------
    871 
    872     # build the library itself with the same instrumentation
    873     ./configure --enable-static --disable-shared --enable-asserts \
    874                 CC=clang \
    875                 CFLAGS="-g -O1 -fsanitize=fuzzer-no-link,address,undefined \
    876                         -fno-sanitize-recover=all -fprofile-instr-generate \
    877                         -fcoverage-mapping"
    878     make -C src/microhttpd
    879 
    880     clang -g -O1 -DFUZZ_NO_MAIN \
    881         -fsanitize=fuzzer,address,undefined -fno-sanitize-recover=all \
    882         -I. -Isrc/include -Isrc/microhttpd -Isrc/fuzz \
    883         -o fuzz_request src/fuzz/fuzz_request.c \
    884         src/microhttpd/.libs/libmicrohttpd.a -lpthread
    885 
    886     mkdir -p CORPUS && ./fuzz_request --help >/dev/null 2>&1 || true
    887     ./fuzz_request CORPUS src/fuzz/corpus \
    888         -max_len=8192 -rss_limit_mb=4096 -timeout=20
    889 
    890     # minimise a crash found by libFuzzer
    891     ./fuzz_request -minimize_crash=1 -runs=100000 crash-<hash>
    892 
    893 7.2 AFL++
    894 ---------
    895 
    896     export CC=afl-clang-lto AFL_USE_ASAN=1 AFL_USE_UBSAN=1
    897     ./configure --enable-static --disable-shared --enable-asserts
    898     make -C src/microhttpd
    899 
    900     afl-clang-lto -g -O1 -DFUZZ_NO_MAIN \
    901         -I. -Isrc/include -Isrc/microhttpd -Isrc/fuzz \
    902         -o fuzz_request src/fuzz/fuzz_request.c \
    903         $(afl-config --libdir 2>/dev/null || echo /usr/local/lib/afl)/afl-compiler-rt.o \
    904         /usr/local/lib/afl/libAFLDriver.a \
    905         src/microhttpd/.libs/libmicrohttpd.a -lpthread
    906 
    907     afl-fuzz -i src/fuzz/corpus -o findings -- ./fuzz_request @@
    908 
    909 (`libAFLDriver.a` provides a `main()` that reads the file named on the
    910 command line and calls LLVMFuzzerTestOneInput(); that is why
    911 `-DFUZZ_NO_MAIN` is required.  With `AFL_LLVM_PERSISTENT` /
    912 `__AFL_LOOP` the same binary can be used in persistent mode.)
    913 
    914 7.3 OSS-Fuzz
    915 ------------
    916 
    917 Ready to go: see `../../contrib/oss-fuzz/` and its README.  That
    918 directory holds the OSS-Fuzz `build.sh`, `project.yaml` and `Dockerfile`,
    919 per-harness dictionaries (`dicts/fuzz_*.dict`), per-harness `.options`
    920 files (`max_len`, `dict`) and `make_seed_corpus.sh`, which packages
    921 `corpus/` — including the `corpus/known-findings/` reproducers of the
    922 findings that are already *fixed*, so that they become permanent
    923 regressions — into the `<fuzzer>_seed_corpus.zip` files OSS-Fuzz
    924 expects.
    925 
    926 `build.sh` configures out of tree with
    927 
    928     --enable-static --disable-shared --with-pic --enable-fuzzing
    929     --enable-asserts --disable-https --disable-curl --disable-doc
    930     --disable-examples --disable-tools --enable-build-type=neutral
    931 
    932 honouring OSS-Fuzz's `$CFLAGS` (no `--enable-sanitizers`: OSS-Fuzz
    933 supplies the instrumentation), and then compiles each harness exactly as
    934 in 7.1 but against `$LIB_FUZZING_ENGINE`.  HTTPS is off on purpose — the
    935 harnesses never speak TLS, and leaving GnuTLS out is what makes the
    936 MemorySanitizer build possible.
    937 
    938 Two things to know before reading a report from there:
    939 
    940   * the request-body oracle of 2.4 is **inactive** under libFuzzer.  It
    941     is gated on `fuzz_pristine`, which nothing sets when `FUZZ_NO_MAIN`
    942     is defined — correctly so, since libFuzzer's mutations invalidate the
    943     declared ground truth.  Pure framing/desync defects (5.3) therefore
    944     remain the job of the built-in driver, i.e. of `make check`;
    945   * `primary_contact` in `project.yaml` is a placeholder.  OSS-Fuzz needs
    946     an address the maintainer controls; it has to be filled in before the
    947     project can be submitted.
    948 
    949 The token list in `fuzz_common.h` (`fuzz_interesting_str`) is the
    950 generator's equivalent of those dictionaries; the two are intentionally
    951 similar but are not generated from each other.
    952 
    953 OSS-Fuzz is deliberately not part of `contrib/ci/jobs/`; the bounded
    954 in-tree equivalents for CI are `make -C src/fuzz check` and
    955 `make -C src/fuzz check-corpus`.
    956 
    957 
    958 -------------------------------------------------------------------
    959 8. Adding a harness
    960 -------------------------------------------------------------------
    961 
    962 Create `fuzz_<name>.c` with
    963 
    964     #define FUZZ_HARNESS_NAME "fuzz_<name>"
    965     #include "fuzz_common.h"
    966 
    967 and implement
    968 
    969     int    LLVMFuzzerTestOneInput (const uint8_t *, size_t);
    970     static size_t fuzz_generate (struct fuzz_rng *, uint8_t *, size_t);
    971     static size_t fuzz_seed_count (void);
    972     static const uint8_t *fuzz_seed_get (size_t, size_t *);
    973 
    974 then add it to `check_PROGRAMS` in Makefile.am.  Report non-crashing
    975 findings with `fuzz_report_finding("...")`, which dumps the reproducer
    976 and aborts.
    977 
    978 Two rules learnt the hard way:
    979 
    980   * do not violate documented *preconditions* of the function under
    981     test (e.g. MHD_str_remove_token_caseless_() asserts that the token
    982     contains no space, tab, comma or NUL) -- otherwise the harness only
    983     finds its own bugs;
    984 
    985   * never evaluate a macro argument twice when it contains a PRNG call.