|
GNU libmicrohttpd 1.0.9
|
In-process fuzzer for MHD's HTTPS/TLS integration layer. More...

Go to the source code of this file.
Macros | |
| #define | FUZZ_HARNESS_NAME "fuzz_tls" |
Functions | |
| int | LLVMFuzzerTestOneInput (const uint8_t *data, size_t size) |
| static size_t | fuzz_generate (struct fuzz_rng *rng, uint8_t *buf, size_t cap) |
| static size_t | fuzz_seed_count (void) |
| static const uint8_t * | fuzz_seed_get (size_t idx, size_t *len) |
Variables | |
| static const uint8_t | no_https_seed [] = { 0 } |
In-process fuzzer for MHD's HTTPS/TLS integration layer.
The target is MHD's own TLS plumbing, not GnuTLS. GnuTLS has its own OSS-Fuzz project and its record parser is not our bug surface, so only a small part of the input budget is spent throwing raw bytes at a TLS socket. What this harness really exercises is
MHD_USE_TLS daemon option surface: MHD_OPTION_HTTPS_MEM_KEY, MHD_OPTION_HTTPS_MEM_CERT, MHD_OPTION_HTTPS_MEM_TRUST, MHD_OPTION_HTTPS_MEM_DHPARAMS, MHD_OPTION_HTTPS_PRIORITIES, MHD_OPTION_HTTPS_PRIORITIES_APPEND, MHD_OPTION_HTTPS_CRED_TYPE, MHD_OPTION_HTTPS_KEY_PASSWORD, MHD_OPTION_TLS_NO_ALPN and the SNI callback MHD_OPTION_HTTPS_CERT_CALLBACK – with malformed PEM blobs, mismatched key/certificate pairs, bogus priority strings, credential types MHD does not support, and an SNI callback that fails or answers with garbage;psk_gnutls_adapter(), which is the one place where MHD takes a buffer straight from an application callback and hands it to GnuTLS. See the "TLS-PSK" note below for why one input bit turns the whole scenario on rather than four independent ones;gnutls_bye() shutdown path that the connection uses afterwards.To reach that last group the harness contains a real, in-process GnuTLS client. Both ends of an AF_UNIX socketpair() are non-blocking and everything runs in one thread: the client's gnutls_handshake() is called until it answers GNUTLS_E_AGAIN, then the daemon is pumped with MHD_run(), and so on. No threads, no ports, no TCP stack, and – because the client's handshake timeout is set to GNUTLS_INDEFINITE_TIMEOUT – no wall clock either.
Input format (see README):
byte 0 certificate/key pair selector (valid, mismatched, truncated, garbage, absent, or built from the fuzzer's own bytes) byte 1 which TLS daemon options to pass at all (bitmask) 0x01 MHD_OPTION_HTTPS_MEM_TRUST 0x02 MHD_OPTION_HTTPS_MEM_DHPARAMS 0x04 MHD_OPTION_HTTPS_CERT_CALLBACK (the SNI callback) 0x08 MHD_OPTION_HTTPS_PRIORITIES[_APPEND] 0x10 MHD_OPTION_HTTPS_CRED_TYPE 0x20 MHD_OPTION_TLS_NO_ALPN 0x40 MHD_OPTION_HTTPS_KEY_PASSWORD 0x80 use _PRIORITIES_APPEND instead of _PRIORITIES byte 2 priority string selector byte 3 bits 0-2 credential type (certificate, PSK, anon, SRP, IA, and two values GnuTLS does not define) bits 3-5 SNI callback behaviour bit 6 trust blob: CA certificate or garbage bit 7 DH parameters: valid or garbage byte 4 bits 0-1 client mode 0 raw bytes, 1 real TLS client, 2 real client, handshake abandoned half way, 3 raw bytes shaped like TLS records bit 2 client sends a server name (SNI) bit 3 client presents a client certificate bits 4-5 client priority string selector bit 6 gnutls_bye() before closing bit 7 shutdown(SHUT_WR) before closing byte 5 bits 0-3 MHD_OPTION_CONNECTION_MEMORY_LIMIT selector bits 4-5 event loop: MHD_run() vs MHD_get_fdset*() + MHD_run_from_select*() bit 6 hand the daemon one more connection at the very end and stop it without running the loop again, so that a TLS session is torn down for a connection MHD never started bit 7 unused byte 6 handler behaviour and introspection bits 0-1 response constructor bit 2 MHD_get_connection_info() for the TLS members bit 3 MHD_get_daemon_info() bit 4 MHD_set_connection_option() bit 5 answer 403 instead of 200 bit 6 add a response header bit 7 MHD_quiesce_daemon() before stopping byte 7 bits 0-2 handshake round budget (client mode 2) bit 3 pass the HTTPS options to a daemon started without MHD_USE_TLS bit 4 MHD_ALLOW_UPGRADE bits 5-7 extra pump rounds byte 8 how many bytes of the segment stream are spliced into the fuzzer-built PEM blobs (see cred_tbl entries 12 and 13) byte 9 bits 0-2 the server name the client presents, as an index into a small built-in table; an op 1 segment overrides it. In the TLS-PSK scenario that same string is also the PSK identity the client sends, so an op 1 segment gives byte level control over the username that reaches psk_gnutls_adapter() bits 3-5 behaviour of the PSK credentials callback bit 6 the client offers PSK credentials bit 7 run the TLS-PSK scenario (see below) byte 10. a sequence of segments, each introduced by a little-endian 16 bit header (op << 14) | length op 0 send the payload op 1 the payload is the server name the next client connection presents; nothing is sent op 2 send the payload and pump the daemon extra rounds op 3 close the connection, open a fresh one (which means a fresh handshake), then send
All ten configuration bytes are mandatory; a shorter input is rejected.
The segment encoding is byte-for-byte the one fuzz_request uses, and corpus/ is shared by every harness in this directory (see README section 3), so the seeds below deliberately contain no op 1 segment: fuzz_request reads op 1 as the declared decoded request body of its own body oracle, and would report a spurious finding when it replays a fuzz_tls seed. That is what byte 9 is for. Inputs the generator or a mutator produces may use op 1 freely – they never end up in corpus/.
TLS-PSK. Reaching psk_gnutls_adapter() needs four unrelated things to be true at the same time: the daemon's credential type has to be #GNUTLS_CRD_PSK, both ends need a priority string that actually has a PSK key exchange in it ("NORMAL" does not), the daemon needs MHD_OPTION_GNUTLS_PSK_CRED_HANDLER, and the client has to offer a PSK identity. Spread over four independent input bits that combination comes up once in a few hundred thousand inputs, and an 8 hour campaign duly left the function at zero coverage. Bit 7 of byte 9 therefore switches the whole scenario on at once and the remaining PSK bits only choose between its variants.
The variants are the branches of psk_gnutls_adapter() itself: the callback is missing, fails, or answers 0 with a key of a workable size, of 4 KiB, of zero size, of one byte less than the MHD_PSK_MIN_SIZE the adapter enforces, and of a size that exceeds UINT_MAX. The two undersized ones and #PSK_OK bracket that minimum from both sides – #PSK_OK is exactly MHD_PSK_MIN_SIZE bytes – which is what makes an off-by-one in the check visible.
One behaviour is deliberately not offered: answering 0 without writing the two output parameters. MHD would then read uninitialised memory, but the application has broken the documented contract, so a report from that would be a harness bug rather than an MHD bug.
The certificates and keys are the ones from src/testcurl/https/tls_test_keys.h (the CA certificate, the CA-signed server certificate with its key, and the self-signed server certificate with its key), reproduced verbatim so that this harness stays a single translation unit. The DH parameters are RFC 3526 group 14, as emitted by certtool --get-dh-params --sec-param medium.
The whole harness is guarded by #HTTPS_SUPPORT: contrib/oss-fuzz/ build.sh configures --disable-https (that is what makes the MemorySanitizer build possible), and in such a build this file must still compile to a valid, trivially passing fuzz target.
Definition in file fuzz_tls.c.
| #define FUZZ_HARNESS_NAME "fuzz_tls" |
Definition at line 178 of file fuzz_tls.c.
|
static |
Definition at line 2799 of file fuzz_tls.c.
|
static |
Definition at line 2815 of file fuzz_tls.c.
|
static |
Definition at line 2822 of file fuzz_tls.c.
References no_https_seed.
| int LLVMFuzzerTestOneInput | ( | const uint8_t * | data, |
| size_t | size ) |
The fuzz target. Signature is the libFuzzer one on purpose, so that the very same harness can be linked with libFuzzer or AFL++ later.
< see queue_unprocessed_conn()
Definition at line 2788 of file fuzz_tls.c.
References data, and fuzz_ignore_sigpipe().
Referenced by fuzz_run_file(), and main().


|
static |
Definition at line 2811 of file fuzz_tls.c.
Referenced by fuzz_seed_get().