GNU libmicrohttpd 1.0.9
Loading...
Searching...
No Matches
fuzz_request.c File Reference

End-to-end in-process fuzzer for the MHD request parser. More...

#include "fuzz_common.h"
#include <microhttpd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/select.h>
Include dependency graph for fuzz_request.c:

Go to the source code of this file.

Macros

#define _GNU_SOURCE   1
 
#define FUZZ_HARNESS_NAME   "fuzz_request"
 
#define MAX_SEGMENTS   96
 
#define MAX_CONNECTIONS   8
 
#define RESP_BUF_SIZE   16384
 
#define GEN_BUF_SIZE   8192
 
#define MAX_EXPECT_BODY   4096
 
#define RESP_KIND_COUNT   16
 
#define DAUTH_VARIANT_COUNT   10
 
#define MAX_DIGEST_BIN   64
 
#define DIGEST_REALM   "TestRealm"
 
#define DIGEST_USER   "user"
 
#define DIGEST_PASS   "pass"
 
#define RESP_BODY_LEN   (sizeof (resp_body) - 1)
 
#define HDR_COUNT   (sizeof (hdr_names) / sizeof (hdr_names[0]))
 
#define DAUTH_CHALLENGE   0
 
#define DAUTH_PASSED   1
 
#define DAUTH_STALE   2
 
#define P_END   { 0, NULL }
 

Enumerations

enum  gen_shape {
  SHAPE_PLAIN = 0 , SHAPE_NOHDR_QARG , SHAPE_CL_BODY , SHAPE_CHUNKED ,
  SHAPE_DIGEST_SIMPLE , SHAPE_DIGEST_REPLAY , SHAPE_BASIC , SHAPE_POST_FORM ,
  SHAPE_WEIRD , SHAPE_COUNT
}
 

Functions

static void print_stats (void)
 
static enum MHD_Result kv_iter (void *cls, enum MHD_ValueKind kind, const char *key, const char *value)
 
static void pending_resume_add (struct MHD_Connection *c)
 
static void pending_resume_drop (const struct MHD_Connection *c)
 
static int pending_resume_flush (void)
 
static ssize_t crc_cb (void *cls, uint64_t pos, char *buf, size_t max)
 
static void crc_free (void *cls)
 
static void upgrade_cb (void *cls, struct MHD_Connection *connection, void *req_cls, const char *extra_in, size_t extra_in_size, MHD_socket sock, struct MHD_UpgradeResponseHandle *urh)
 
static int make_memfd (void)
 
static int make_pipe_fd (void)
 
static void decorate_response (struct MHD_Response *r)
 
static struct MHD_Responsemake_response (void)
 
static enum MHD_Result kv_iter_n (void *cls, enum MHD_ValueKind kind, const char *key, size_t key_size, const char *value, size_t value_size)
 
static enum MHD_Result post_iter (void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size)
 
static void oracle_check_body (struct hstate *hs, const char *data, size_t size)
 
static void call_calc_helpers (void)
 
static int run_digest_check (struct MHD_Connection *connection)
 
static enum MHD_Result queue_auth_challenge (struct MHD_Connection *connection, int stale)
 
static void exercise_connection_api (struct MHD_Connection *connection)
 
static void suspend_maybe (struct MHD_Connection *connection)
 
static enum MHD_Result ahc (void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **req_cls)
 
static void completed_cb (void *cls, struct MHD_Connection *connection, void **req_cls, enum MHD_RequestTerminationCode toe)
 
static void panic_cb (void *cls, const char *file, unsigned int line, const char *reason)
 
static void drain_and_harvest (int sock)
 
static void run_once (struct MHD_Daemon *d)
 
static void pump (struct MHD_Daemon *d, int sock, unsigned int rounds)
 
static size_t expand_nonce (const uint8_t *in, size_t in_len, uint8_t *out, size_t out_cap)
 
static void send_all (struct MHD_Daemon *d, int sock, const uint8_t *data, size_t len)
 
static int new_connection (struct MHD_Daemon *d, int *sock)
 
static void close_connection (struct MHD_Daemon *d, int *sock)
 
int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
 
static void sb_raw (struct sbuf *b, const void *v, size_t n)
 
static void sb_str (struct sbuf *b, const char *s)
 
static void sb_u64 (struct sbuf *b, uint64_t v, int hex, unsigned int min_digits)
 
static void gen_query (struct fuzz_rng *rng, struct sbuf *b, int force_trailing_novalue)
 
static void gen_headers (struct fuzz_rng *rng, struct sbuf *b, unsigned int n)
 
static void gen_chunked_body (struct fuzz_rng *rng, struct sbuf *b, int oracle)
 
static void gen_digest_header (struct fuzz_rng *rng, struct sbuf *b, const char *uri, int use_nonce_token, int overlong_response, int prefer_valid)
 
static int gen_one_request (struct fuzz_rng *rng, struct sbuf *b, enum gen_shape shape, int second_of_pair)
 
static void emit_segments (struct fuzz_rng *rng, struct sbuf *out, const uint8_t *body, size_t body_len, int new_conn_first)
 
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 struct fuzz_cfg cfg
 
static char nonce_val [192]
 
static size_t nonce_len
 
static uint8_t expect_body [MAX_EXPECT_BODY]
 
static size_t expect_body_len
 
static int oracle_on
 
static unsigned long stat_daemons
 
static unsigned long stat_handler_calls
 
static unsigned long stat_final_calls
 
static unsigned long stat_body_bytes
 
static unsigned long stat_challenges
 
static unsigned long stat_auth_ok
 
static int stats_registered
 
static char resp_buf [RESP_BUF_SIZE]
 
static size_t resp_len
 
static const size_t mem_limit_tbl []
 
static const int discipline_tbl [] = { -3, -2, -1, 0, 1, 2 }
 
static int min_discipline = -3
 
static int min_discipline_read
 
static int forced_shape = -1
 
static int forced_shape_read
 
static size_t min_mem_limit
 
static int min_mem_limit_read
 
static enum MHD_DigestAuthMultiAlgo3 algo_tbl []
 
static const unsigned int nnc_tbl [] = { 4, 1, 8, 64 }
 
static const char digest_rnd [32]
 
static struct MHD_Daemoncur_daemon
 
static struct MHD_Connectionpending_resume [MAX_CONNECTIONS]
 
static int tearing_down
 
static const char resp_body [] = "hello world, this is the response body"
 
static const char *const hdr_names []
 
static const char *const hdr_values []
 
static enum MHD_DigestAuthAlgorithm algo1_tbl []
 
static enum MHD_DigestAuthAlgo3 algo3_tbl []
 
static const char *const gen_methods []
 
static const char *const gen_versions []
 
static const char *const gen_targets []
 
static const char *const gen_hdr_names []
 
static const char *const gen_hdr_values []
 
static const char *const gen_algos []
 
static const char *const gen_ctypes []
 
static const struct seed_def seeds []
 
static uint8_t seed_render_buf [4096]
 

Detailed Description

End-to-end in-process fuzzer for the MHD request parser.

Author
Christian Grothoff

A real struct MHD_Daemon is created with MHD_USE_NO_LISTEN_SOCKET and driven through a socketpair() that is handed to MHD with MHD_add_connection(). The daemon runs in external-polling mode, so everything happens in the fuzzer's thread: fully deterministic, no TCP stack, no ports, no races.

Input format (see README):

byte 0 configuration: connection memory limit byte 1 configuration: handler behaviour bitmask byte 2 configuration: client discipline / insanity level byte 3 configuration: digest-auth parameters byte 4 response construction: which MHD_create_response_*() to use, how many response headers to add, footers, header-manipulation API byte 5 which authentication entry point to call: the v1/v2 compatibility wrappers, the username/request-info queries, or the differing fail-response variants byte 6 event-loop mode (MHD_run() vs MHD_get_fdset*() + MHD_run_from_select*()) and the connection-introspection calls byte 7 suspend/resume and HTTP "Upgrade" byte 8 seed for the generated response header names/values byte 9 seed for the content-reader callback behaviour byte 10. a sequence of send-segments, each introduced by a little-endian 16 bit header (op << 14) | length op 0 send the payload op 1 the payload is the expected decoded request body (ground truth for the body oracle); nothing is sent op 2 send the payload and pump the daemon extra rounds op 3 close the connection, open a fresh one, send

All ten configuration bytes are always present; an input shorter than that is rejected outright. The all-zero configuration is the plainest one – a static two byte buffer response, MHD_run() as the event loop, no suspend, no upgrade, no extra introspection – so zeroing bytes 4-9 of any input reduces it to what the harness did before those bytes existed.

Splitting the byte stream into explicit segments matters: MHD's parser is incremental and several past bugs only showed up for particular split points.

The literal ASCII token "%%NONCE%%" inside a segment is replaced, at send time, by the most recent nonce="..." value seen in a response from the daemon. This is what allows the fuzzer to walk through the Digest-Auth challenge/response handshake and reach the code that is only executed for a valid nonce.

Definition in file fuzz_request.c.

Macro Definition Documentation

◆ _GNU_SOURCE

#define _GNU_SOURCE   1

Definition at line 83 of file fuzz_request.c.

◆ DAUTH_CHALLENGE

#define DAUTH_CHALLENGE   0

Outcome of run_digest_check().

Definition at line 905 of file fuzz_request.c.

Referenced by run_digest_check().

◆ DAUTH_PASSED

#define DAUTH_PASSED   1

Definition at line 906 of file fuzz_request.c.

Referenced by ahc(), and run_digest_check().

◆ DAUTH_STALE

#define DAUTH_STALE   2

Definition at line 907 of file fuzz_request.c.

Referenced by ahc(), and run_digest_check().

◆ DAUTH_VARIANT_COUNT

#define DAUTH_VARIANT_COUNT   10

Number of authentication entry points selectable by byte 5.

Definition at line 123 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ DIGEST_PASS

#define DIGEST_PASS   "pass"

Definition at line 129 of file fuzz_request.c.

Referenced by call_calc_helpers(), and run_digest_check().

◆ DIGEST_REALM

#define DIGEST_REALM   "TestRealm"

◆ DIGEST_USER

#define DIGEST_USER   "user"

Definition at line 128 of file fuzz_request.c.

Referenced by call_calc_helpers(), gen_digest_header(), and run_digest_check().

◆ FUZZ_HARNESS_NAME

#define FUZZ_HARNESS_NAME   "fuzz_request"

Definition at line 86 of file fuzz_request.c.

Referenced by print_stats().

◆ GEN_BUF_SIZE

#define GEN_BUF_SIZE   8192

Definition at line 117 of file fuzz_request.c.

Referenced by fuzz_generate().

◆ HDR_COUNT

#define HDR_COUNT   (sizeof (hdr_names) / sizeof (hdr_names[0]))

Definition at line 403 of file fuzz_request.c.

Referenced by decorate_response().

◆ MAX_CONNECTIONS

#define MAX_CONNECTIONS   8

◆ MAX_DIGEST_BIN

#define MAX_DIGEST_BIN   64

Largest digest MHD_digest_auth_calc_userdigest() can produce.

Definition at line 125 of file fuzz_request.c.

Referenced by call_calc_helpers(), and run_digest_check().

◆ MAX_EXPECT_BODY

#define MAX_EXPECT_BODY   4096

◆ MAX_SEGMENTS

#define MAX_SEGMENTS   96

Definition at line 114 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ P_END

#define P_END   { 0, NULL }

Definition at line 2756 of file fuzz_request.c.

◆ RESP_BODY_LEN

#define RESP_BODY_LEN   (sizeof (resp_body) - 1)

Definition at line 367 of file fuzz_request.c.

Referenced by make_memfd(), make_pipe_fd(), and make_response().

◆ RESP_BUF_SIZE

#define RESP_BUF_SIZE   16384

Definition at line 116 of file fuzz_request.c.

Referenced by drain_and_harvest().

◆ RESP_KIND_COUNT

#define RESP_KIND_COUNT   16

Number of entries in the response-construction table.

Definition at line 121 of file fuzz_request.c.

Enumeration Type Documentation

◆ gen_shape

enum gen_shape
Enumerator
SHAPE_PLAIN 
SHAPE_NOHDR_QARG 
SHAPE_CL_BODY 
SHAPE_CHUNKED 
SHAPE_DIGEST_SIMPLE 
SHAPE_DIGEST_REPLAY 
SHAPE_BASIC 
SHAPE_POST_FORM 
SHAPE_WEIRD 
SHAPE_COUNT 

Definition at line 2438 of file fuzz_request.c.

Function Documentation

◆ ahc()

◆ call_calc_helpers()

static void call_calc_helpers ( void )
static

Exercise the digest helpers that take no connection: MHD_digest_get_hash_size(), MHD_digest_auth_calc_userdigest(), MHD_digest_auth_calc_userhash() and MHD_digest_auth_calc_userhash_hex().

The output buffers are heap allocations of exactly the size that is passed to MHD, and that size is deliberately varied down to zero. A correct implementation must reject every buffer that is too small; if it instead writes the full digest, ASAN's redzone catches it immediately. This is the shape of the bug fixed in commit 5a73c1ae, where an over-long userhash overflowed a fixed-size decode buffer.

Definition at line 924 of file fuzz_request.c.

References algo3_tbl, cfg, DIGEST_PASS, DIGEST_REALM, DIGEST_USER, MAX_DIGEST_BIN, MHD_digest_auth_calc_userdigest(), MHD_digest_auth_calc_userhash(), MHD_digest_auth_calc_userhash_hex(), MHD_digest_get_hash_size(), and NULL.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ close_connection()

static void close_connection ( struct MHD_Daemon * d,
int * sock )
static

Definition at line 1869 of file fuzz_request.c.

References pump().

Referenced by LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ completed_cb()

static void completed_cb ( void * cls,
struct MHD_Connection * connection,
void ** req_cls,
enum MHD_RequestTerminationCode toe )
static

Definition at line 1587 of file fuzz_request.c.

References MHD_destroy_post_processor(), NULL, and pending_resume_drop().

Referenced by LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ crc_cb()

static ssize_t crc_cb ( void * cls,
uint64_t pos,
char * buf,
size_t max )
static

Definition at line 421 of file fuzz_request.c.

References MHD_CONTENT_READER_END_OF_STREAM, and MHD_CONTENT_READER_END_WITH_ERROR.

Referenced by make_response().

Here is the caller graph for this function:

◆ crc_free()

static void crc_free ( void * cls)
static

Definition at line 452 of file fuzz_request.c.

Referenced by make_response().

Here is the caller graph for this function:

◆ decorate_response()

static void decorate_response ( struct MHD_Response * r)
static

Add response headers, footers and option flags as selected by byte 4 and the header seed in byte 8.

Footers are only meaningful for a chunked response – MHD emits them as trailers after the last chunk – which is why they are worth fuzzing together with the callback-based response kinds.

Definition at line 555 of file fuzz_request.c.

References cfg, HDR_COUNT, hdr_names, hdr_values, kv_iter(), MHD_add_response_footer(), MHD_add_response_header(), MHD_del_response_header(), MHD_get_response_header(), MHD_get_response_headers(), MHD_RF_HEAD_ONLY_RESPONSE, MHD_RF_HTTP_VERSION_1_0_RESPONSE, MHD_RF_INSANITY_HEADER_CONTENT_LENGTH, MHD_RF_SEND_KEEP_ALIVE_HEADER, MHD_RO_END, MHD_set_response_options(), and NULL.

Referenced by ahc(), and make_response().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ drain_and_harvest()

static void drain_and_harvest ( int sock)
static

Read whatever the daemon has produced so far and look for a fresh Digest-Auth nonce in it.

Definition at line 1635 of file fuzz_request.c.

References nonce_len, nonce_val, NULL, resp_buf, RESP_BUF_SIZE, and resp_len.

Referenced by pump().

Here is the caller graph for this function:

◆ emit_segments()

static void emit_segments ( struct fuzz_rng * rng,
struct sbuf * out,
const uint8_t * body,
size_t body_len,
int new_conn_first )
static

Serialise body into the segment format understood by LLVMFuzzerTestOneInput(), starting at out[*out_len].

Definition at line 2588 of file fuzz_request.c.

References fuzz_below(), fuzz_chance(), and sb_raw().

Referenced by fuzz_generate().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ exercise_connection_api()

static void exercise_connection_api ( struct MHD_Connection * connection)
static

◆ expand_nonce()

static size_t expand_nonce ( const uint8_t * in,
size_t in_len,
uint8_t * out,
size_t out_cap )
static

Copy in to out, expanding every occurrence of the ASCII token "%%NONCE%%" to the harvested nonce.

Returns
number of bytes written to out

Definition at line 1780 of file fuzz_request.c.

References nonce_len, and nonce_val.

Referenced by LLVMFuzzerTestOneInput().

Here is the caller graph for this function:

◆ fuzz_generate()

static size_t fuzz_generate ( struct fuzz_rng * rng,
uint8_t * buf,
size_t cap )
static

◆ fuzz_seed_count()

static size_t fuzz_seed_count ( void )
static

Definition at line 3023 of file fuzz_request.c.

References seeds.

◆ fuzz_seed_get()

static const uint8_t * fuzz_seed_get ( size_t idx,
size_t * len )
static

Definition at line 3030 of file fuzz_request.c.

References NULL, sb_raw(), seed_render_buf, and seeds.

Here is the call graph for this function:

◆ gen_chunked_body()

static void gen_chunked_body ( struct fuzz_rng * rng,
struct sbuf * b,
int oracle )
static

Emit a chunked body. When oracle is non-zero the body is strictly RFC 9112 conformant and the decoded payload is recorded in expect_body, so that the harness can verify what MHD hands to the application. Chunk extensions are emitted frequently on purpose.

Definition at line 2314 of file fuzz_request.c.

References expect_body, expect_body_len, fuzz_below(), fuzz_chance(), MAX_EXPECT_BODY, sb_raw(), sb_str(), and sb_u64().

Referenced by gen_one_request().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_digest_header()

static void gen_digest_header ( struct fuzz_rng * rng,
struct sbuf * b,
const char * uri,
int use_nonce_token,
int overlong_response,
int prefer_valid )
static

Definition at line 2374 of file fuzz_request.c.

References DIGEST_REALM, DIGEST_USER, fuzz_below(), fuzz_chance(), gen_algos, sb_raw(), sb_str(), and sb_u64().

Referenced by gen_one_request().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_headers()

static void gen_headers ( struct fuzz_rng * rng,
struct sbuf * b,
unsigned int n )
static

Definition at line 2278 of file fuzz_request.c.

References fuzz_below(), fuzz_chance(), gen_hdr_names, gen_hdr_values, and sb_str().

Referenced by gen_one_request().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_one_request()

static int gen_one_request ( struct fuzz_rng * rng,
struct sbuf * b,
enum gen_shape shape,
int second_of_pair )
static

Build one HTTP request into b.

Returns
non-zero if the request is exactly reproducible, i.e. the expect_body oracle may be used

Definition at line 2460 of file fuzz_request.c.

References expect_body, expect_body_len, fuzz_below(), fuzz_chance(), gen_chunked_body(), gen_ctypes, gen_digest_header(), gen_headers(), gen_methods, gen_query(), gen_targets, gen_versions, MAX_EXPECT_BODY, sb_raw(), sb_str(), sb_u64(), SHAPE_BASIC, SHAPE_CHUNKED, SHAPE_CL_BODY, SHAPE_COUNT, SHAPE_DIGEST_REPLAY, SHAPE_DIGEST_SIMPLE, SHAPE_NOHDR_QARG, SHAPE_PLAIN, SHAPE_POST_FORM, and SHAPE_WEIRD.

Referenced by fuzz_generate().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_query()

static void gen_query ( struct fuzz_rng * rng,
struct sbuf * b,
int force_trailing_novalue )
static

Append a random query string, optionally ending in an argument without '=' (which is the shape needed for the read-buffer shift-back bug).

Definition at line 2246 of file fuzz_request.c.

References fuzz_below(), fuzz_chance(), sb_str(), and sb_u64().

Referenced by gen_one_request().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ kv_iter()

static enum MHD_Result kv_iter ( void * cls,
enum MHD_ValueKind kind,
const char * key,
const char * value )
static

Definition at line 780 of file fuzz_request.c.

References MHD_PostProcessor::cls, MHD_YES, and NULL.

Referenced by ahc(), and decorate_response().

Here is the caller graph for this function:

◆ kv_iter_n()

static enum MHD_Result kv_iter_n ( void * cls,
enum MHD_ValueKind kind,
const char * key,
size_t key_size,
const char * value,
size_t value_size )
static

Length-aware counterpart of kv_iter(), for MHD_get_connection_values_n(). Unlike the NUL-terminated iterator it is handed explicit sizes, so it reads exactly what MHD says is there rather than trusting a terminator.

Definition at line 805 of file fuzz_request.c.

References MHD_PostProcessor::cls, MHD_YES, and NULL.

Referenced by ahc().

Here is the caller graph for this function:

◆ LLVMFuzzerTestOneInput()

int LLVMFuzzerTestOneInput ( const uint8_t * data,
size_t size )

◆ make_memfd()

static int make_memfd ( void )
static

A file descriptor holding RESP_BODY_LEN bytes, backed by anonymous memory so that no filesystem is involved.

Returns
-1 if unavailable, in which case the caller falls back to a buffer response

Definition at line 504 of file fuzz_request.c.

References fd, resp_body, and RESP_BODY_LEN.

Referenced by make_response().

Here is the caller graph for this function:

◆ make_pipe_fd()

static int make_pipe_fd ( void )
static

The read end of a pipe holding RESP_BODY_LEN bytes. The body is far below the pipe capacity, so the write cannot block.

Definition at line 529 of file fuzz_request.c.

References resp_body, and RESP_BODY_LEN.

Referenced by make_response().

Here is the caller graph for this function:

◆ make_response()

static struct MHD_Response * make_response ( void )
static

Build the response body for this iteration.

Every constructor that fails is responsible for nothing: MHD releases neither the buffer nor the file descriptor when it returns NULL (see MHD_create_response_from_buffer_with_free_callback_cls() and MHD_create_response_from_pipe() in response.c), so ownership stays here and the resource has to be released explicitly. Getting this backwards would produce double frees that look exactly like MHD bugs.

Definition at line 610 of file fuzz_request.c.

References cfg, crc_cb(), crc_free(), decorate_response(), fd, MHD_IoVec::iov_base, MHD_IoVec::iov_len, make_memfd(), make_pipe_fd(), MHD_create_response_empty(), MHD_create_response_from_buffer(), MHD_create_response_from_buffer_copy(), MHD_create_response_from_buffer_static(), MHD_create_response_from_buffer_with_free_callback(), MHD_create_response_from_callback(), MHD_create_response_from_data(), MHD_create_response_from_fd(), MHD_create_response_from_fd64(), MHD_create_response_from_fd_at_offset, MHD_create_response_from_iovec(), MHD_create_response_from_pipe(), MHD_RESPMEM_MUST_COPY, MHD_RESPMEM_MUST_FREE, MHD_RESPMEM_PERSISTENT, MHD_RF_NONE, MHD_SIZE_UNKNOWN, NULL, resp_body, and RESP_BODY_LEN.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ new_connection()

static int new_connection ( struct MHD_Daemon * d,
int * sock )
static

Definition at line 1842 of file fuzz_request.c.

References MHD_add_connection(), and MHD_YES.

Referenced by LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ oracle_check_body()

static void oracle_check_body ( struct hstate * hs,
const char * data,
size_t size )
static

Check the body MHD hands to the application against the body that the generator encoded into the request. MHD is allowed to reject the request at any point, but every byte that it does deliver must be the next expected byte. A mismatch means the framing layer (Content-Length or chunked transfer coding) got out of sync, which is exactly the class of bug that enables request smuggling.

Definition at line 865 of file fuzz_request.c.

References data, expect_body, expect_body_len, fuzz_report_finding(), and oracle_on.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ panic_cb()

static void panic_cb ( void * cls,
const char * file,
unsigned int line,
const char * reason )
static

Definition at line 1609 of file fuzz_request.c.

References fuzz_report_finding(), and NULL.

Referenced by LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pending_resume_add()

static void pending_resume_add ( struct MHD_Connection * c)
static

Record c as suspended. Silently ignores the overflow case: the array has one slot per connection the harness can open, so it cannot overflow, and a dropped entry would only cost the resume below.

Definition at line 310 of file fuzz_request.c.

References MAX_CONNECTIONS, NULL, and pending_resume.

Referenced by suspend_maybe().

Here is the caller graph for this function:

◆ pending_resume_drop()

static void pending_resume_drop ( const struct MHD_Connection * c)
static

Forget c without resuming it (it is gone).

Definition at line 327 of file fuzz_request.c.

References MAX_CONNECTIONS, NULL, and pending_resume.

Referenced by completed_cb().

Here is the caller graph for this function:

◆ pending_resume_flush()

static int pending_resume_flush ( void )
static

Resume everything still parked.

Returns
non-zero if at least one connection was resumed, so that the caller knows the daemon needs another round to act on it

Definition at line 344 of file fuzz_request.c.

References MAX_CONNECTIONS, MHD_resume_connection(), NULL, and pending_resume.

Referenced by LLVMFuzzerTestOneInput(), and pump().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ post_iter()

static enum MHD_Result post_iter ( void * cls,
enum MHD_ValueKind kind,
const char * key,
const char * filename,
const char * content_type,
const char * transfer_encoding,
const char * data,
uint64_t off,
size_t size )
static

Definition at line 826 of file fuzz_request.c.

References MHD_PostProcessor::cls, MHD_PostProcessor::content_type, data, MHD_YES, and NULL.

Referenced by ahc().

Here is the caller graph for this function:

◆ print_stats()

static void print_stats ( void )
static

Definition at line 195 of file fuzz_request.c.

References FUZZ_HARNESS_NAME, fuzz_verbose, stat_auth_ok, stat_body_bytes, stat_challenges, stat_daemons, stat_final_calls, and stat_handler_calls.

Referenced by LLVMFuzzerTestOneInput().

Here is the caller graph for this function:

◆ pump()

static void pump ( struct MHD_Daemon * d,
int sock,
unsigned int rounds )
static

Definition at line 1754 of file fuzz_request.c.

References drain_and_harvest(), pending_resume_flush(), and run_once().

Referenced by close_connection(), LLVMFuzzerTestOneInput(), and send_all().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ queue_auth_challenge()

static enum MHD_Result queue_auth_challenge ( struct MHD_Connection * connection,
int stale )
static

Send the "authentication required" reply through the variant of the API selected by byte 5. The v1/v2 forms take a plain response and an algorithm rather than the qop/algo pair of the v3 form.

Definition at line 1178 of file fuzz_request.c.

References algo1_tbl, cfg, DIGEST_REALM, MHD_create_response_from_buffer_static(), MHD_destroy_response(), MHD_NO, MHD_queue_auth_fail_response(), MHD_queue_auth_fail_response2(), MHD_queue_auth_required_response3(), MHD_queue_basic_auth_fail_response(), MHD_queue_basic_auth_required_response3(), MHD_YES, and NULL.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ run_digest_check()

static int run_digest_check ( struct MHD_Connection * connection)
static

Run the digest-authentication entry point selected by byte 5.

Variant 0 is MHD_digest_auth_check3(), which is the plainest one and what the all-zero configuration selects. Variants 1-5 are the v1/v2 compatibility wrappers and the pre-computed-digest forms, which do their own buffer sizing and are therefore worth driving directly. Variants 6-8 are the query functions, which parse the client's Authorization header but return no verdict, so they run in addition to check3().

Returns
one of DAUTH_CHALLENGE, DAUTH_PASSED, DAUTH_STALE

Definition at line 985 of file fuzz_request.c.

References algo1_tbl, cfg, MHD_DigestAuthInfo::cnonce_len, MHD_PostProcessor::connection, DAUTH_CHALLENGE, DAUTH_PASSED, DAUTH_STALE, DIGEST_PASS, DIGEST_REALM, DIGEST_USER, MAX_DIGEST_BIN, MHD_DAUTH_NONCE_STALE, MHD_DAUTH_OK, MHD_DIGEST_ALG_MD5, MHD_DIGEST_ALG_SHA256, MHD_DIGEST_AUTH_ALGO3_MD5, MHD_DIGEST_AUTH_ALGO3_MD5_SESSION, MHD_DIGEST_AUTH_ALGO3_SHA256, MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION, MHD_DIGEST_AUTH_ALGO3_SHA512_256, MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION, MHD_digest_auth_calc_userdigest(), MHD_digest_auth_check(), MHD_digest_auth_check2(), MHD_digest_auth_check3(), MHD_digest_auth_check_digest(), MHD_digest_auth_check_digest2(), MHD_digest_auth_check_digest3(), MHD_digest_auth_get_request_info3(), MHD_digest_auth_get_username(), MHD_digest_auth_get_username3(), MHD_DIGEST_AUTH_MULT_ALGO3_ANY_NON_SESSION, MHD_digest_get_hash_size(), MHD_free(), MHD_INVALID_NONCE, MHD_MD5_DIGEST_SIZE, MHD_YES, MHD_DigestAuthInfo::nc, NULL, MHD_DigestAuthInfo::opaque, MHD_DigestAuthInfo::opaque_len, MHD_DigestAuthInfo::realm, MHD_DigestAuthInfo::realm_len, MHD_DigestAuthInfo::userhash_hex, MHD_DigestAuthUsernameInfo::userhash_hex, MHD_DigestAuthInfo::userhash_hex_len, MHD_DigestAuthUsernameInfo::userhash_hex_len, MHD_DigestAuthInfo::username, MHD_DigestAuthUsernameInfo::username, MHD_DigestAuthInfo::username_len, and MHD_DigestAuthUsernameInfo::username_len.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ run_once()

static void run_once ( struct MHD_Daemon * d)
static

Advance the daemon by one cycle, through the event-loop API selected by byte 6 of the input.

Mode 0 is MHD_run(), which is what the harness used before the extension and which does the descriptor bookkeeping internally. Modes 1-3 drive the daemon the way an application with its own event loop does: MHD_get_fdset*() to collect the descriptors, select(), then MHD_run_from_select*(). That is the most common production integration and none of it was reachable before.

The select() timeout is always zero. The harness is single threaded and whatever the daemon is waiting for has already been written into the socketpair, so blocking would only burn wall clock; select() is called purely to populate the ready sets.

Definition at line 1692 of file fuzz_request.c.

References cfg, MHD_get_fdset, MHD_get_fdset2(), MHD_get_timeout(), MHD_get_timeout64(), MHD_get_timeout64s(), MHD_get_timeout_i(), MHD_INVALID_SOCKET, MHD_run(), MHD_run_from_select, MHD_run_from_select2(), MHD_UNSIGNED_LONG_LONG, and MHD_YES.

Referenced by pump().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sb_raw()

static void sb_raw ( struct sbuf * b,
const void * v,
size_t n )
static

Definition at line 2150 of file fuzz_request.c.

Referenced by emit_segments(), fuzz_generate(), fuzz_seed_get(), gen_chunked_body(), gen_digest_header(), gen_one_request(), sb_str(), and sb_u64().

Here is the caller graph for this function:

◆ sb_str()

static void sb_str ( struct sbuf * b,
const char * s )
static

Definition at line 2162 of file fuzz_request.c.

References sb_raw().

Referenced by gen_chunked_body(), gen_digest_header(), gen_headers(), gen_one_request(), and gen_query().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sb_u64()

static void sb_u64 ( struct sbuf * b,
uint64_t v,
int hex,
unsigned int min_digits )
static

Definition at line 2170 of file fuzz_request.c.

References sb_raw().

Referenced by gen_chunked_body(), gen_digest_header(), gen_one_request(), and gen_query().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ send_all()

static void send_all ( struct MHD_Daemon * d,
int sock,
const uint8_t * data,
size_t len )
static

Definition at line 1811 of file fuzz_request.c.

References data, and pump().

Referenced by LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ suspend_maybe()

static void suspend_maybe ( struct MHD_Connection * connection)
static

Suspend the connection if byte 7 asks for it.

Mode 1 suspends and resumes straight away, which exercises both state transitions without leaving the connection parked. Mode 2 leaves it suspended and records it in pending_resume so that the pump loop resumes it on its next round; completed_cb() drops the record, so a connection MHD has finished with is never resumed after the fact, and the iteration flushes the whole set before stopping the daemon. A connection left suspended makes MHD_stop_daemon() MHD_PANIC().

Definition at line 1344 of file fuzz_request.c.

References cfg, MHD_resume_connection(), MHD_suspend_connection(), pending_resume_add(), and tearing_down.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ upgrade_cb()

static void upgrade_cb ( void * cls,
struct MHD_Connection * connection,
void * req_cls,
const char * extra_in,
size_t extra_in_size,
MHD_socket sock,
struct MHD_UpgradeResponseHandle * urh )
static

Handler for an upgraded ("101 Switching Protocols") connection.

extra_in holds whatever the client had already pipelined behind the request, which is attacker-controlled and therefore read here.

The connection MUST be closed through MHD_upgrade_action(): the socket belongs to the application from this point on and MHD_stop_daemon() cannot complete while an upgraded connection is still outstanding.

Definition at line 470 of file fuzz_request.c.

References cfg, MHD_upgrade_action(), MHD_UPGRADE_ACTION_CLOSE, MHD_UPGRADE_ACTION_CORK_OFF, MHD_UPGRADE_ACTION_CORK_ON, and NULL.

Referenced by ahc().

Here is the call graph for this function:
Here is the caller graph for this function:

Variable Documentation

◆ algo1_tbl

enum MHD_DigestAuthAlgorithm algo1_tbl[]
static
Initial value:

Definition at line 886 of file fuzz_request.c.

Referenced by queue_auth_challenge(), and run_digest_check().

◆ algo3_tbl

◆ algo_tbl

enum MHD_DigestAuthMultiAlgo3 algo_tbl[]
static

◆ cfg

◆ cur_daemon

struct MHD_Daemon* cur_daemon
static

Daemon of the current iteration; needed by MHD_get_daemon_info().

Definition at line 277 of file fuzz_request.c.

Referenced by exercise_connection_api(), and LLVMFuzzerTestOneInput().

◆ digest_rnd

const char digest_rnd[32]
static
Initial value:
=
"\x01\x23\x45\x67\x89\xab\xcd\xef\x01\x23\x45\x67\x89\xab\xcd\xef"
"\xfe\xdc\xba\x98\x76\x54\x32\x10\xfe\xdc\xba\x98\x76\x54\x32\x10"

Fixed entropy so that nonces are reproducible across runs.

Definition at line 259 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ discipline_tbl

const int discipline_tbl[] = { -3, -2, -1, 0, 1, 2 }
static

Definition at line 217 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ expect_body

uint8_t expect_body[MAX_EXPECT_BODY]
static

Ground-truth body of a generated (pristine) request, see README.

Definition at line 180 of file fuzz_request.c.

Referenced by fuzz_generate(), gen_chunked_body(), gen_one_request(), LLVMFuzzerTestOneInput(), and oracle_check_body().

◆ expect_body_len

size_t expect_body_len
static

◆ forced_shape

int forced_shape = -1
static

Restrict the generator to a single grammar shape (see enum gen_shape). -1 (the default) means "pick a random shape every time". Set with MHD_FUZZ_SHAPE=<n>; handy for triage and for regression testing a specific past bug.

Definition at line 235 of file fuzz_request.c.

Referenced by fuzz_generate().

◆ forced_shape_read

int forced_shape_read
static

Definition at line 236 of file fuzz_request.c.

Referenced by fuzz_generate().

◆ gen_algos

const char* const gen_algos[]
static
Initial value:
= {
"MD5", "SHA-256", "SHA-512-256", "MD5-sess", "SHA-256-sess",
"SHA-512-256-sess", "sha-256", "SHA256", "BOGUS", "", "\"SHA-256\"",
"MD5 ", "SHA-1", "xyzzy", "SHA-512", "\"BOGUS\"", "0"
}

Deliberately includes tokens MHD does not know: bug #1 lives here.

Definition at line 2225 of file fuzz_request.c.

Referenced by gen_digest_header().

◆ gen_ctypes

const char* const gen_ctypes[]
static
Initial value:
= {
"application/x-www-form-urlencoded",
"multipart/form-data; boundary=--abc",
"multipart/form-data; boundary=\"XY\"",
"multipart/form-data",
"text/plain"
}

Definition at line 2231 of file fuzz_request.c.

Referenced by gen_one_request().

◆ gen_hdr_names

const char* const gen_hdr_names[]
static
Initial value:
= {
"Host", "Accept", "User-Agent", "Connection", "Cookie", "Content-Type",
"Expect", "TE", "Trailer", "X-Custom", "Referer", "Accept-Encoding",
"content-length", "transfer-encoding", "X-A-Very-Long-Header-Name-Indeed"
}

Definition at line 2212 of file fuzz_request.c.

Referenced by gen_headers().

◆ gen_hdr_values

const char* const gen_hdr_values[]
static
Initial value:
= {
"example.org", "*/*", "fuzz/1.0", "keep-alive", "close",
"a=1; b=2; c", "100-continue", "trailers", "X-Trail", "value",
"", " ", "\ttabbed", "a, b, c", "chunked", "identity, chunked"
}

Definition at line 2218 of file fuzz_request.c.

Referenced by gen_headers().

◆ gen_methods

const char* const gen_methods[]
static
Initial value:
= {
"GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS", "TRACE", "PATCH",
"CONNECT", "BREW", "get", "M-SEARCH", "\tGET", "GET "
}

Definition at line 2195 of file fuzz_request.c.

Referenced by gen_one_request().

◆ gen_targets

const char* const gen_targets[]
static
Initial value:
= {
"/", "/a", "/a/b/c", "/a?x=1", "/a?x=1&y=2", "/?novalue", "/?a=1&b",
"/?a&b&c", "/%41%42", "/a%00b", "/%zz", "/a?%41=%42", "*",
"http://example.org/a", "/a?x=1&novalue", "/..%2f..%2fetc",
"/a?=", "/a?&", "/very/long/path/that/keeps/going/and/going/and/going"
}

Definition at line 2205 of file fuzz_request.c.

Referenced by gen_one_request().

◆ gen_versions

const char* const gen_versions[]
static
Initial value:
= {
"HTTP/1.1", "HTTP/1.0", "HTTP/1.1", "HTTP/1.1", "HTTP/0.9", "HTTP/1.2",
"HTTP/2.0", "http/1.1", "HTTP/1.", "HTTP/11"
}

Definition at line 2200 of file fuzz_request.c.

Referenced by gen_one_request().

◆ hdr_names

const char* const hdr_names[]
static
Initial value:
= {
"X-Fuzz", "Content-Type", "Cache-Control", "Accept-Ranges",
"Connection", "Set-Cookie", "X-\x01" "Bad", "", "Trailer", "Date",
"X-Fuzz", "Server", "Location", "ETag", "Vary", "Age"
}

Response header names and values, indexed by cfg.hdr_seed. The list mixes ordinary headers with ones MHD has to reject or handle specially: an empty name, a name carrying a control character, and a value containing CRLF – the classic response-splitting vector, which MHD_add_response_header() is supposed to refuse. Exercising that validator is the point of this table.

The message-framing headers are deliberately NOT here. MHD generates Content-Length and Transfer-Encoding itself from the response object, so an application that also sets them by hand is lying to the library about its own body: a manual "Transfer-Encoding: chunked" on, say, an iovec response sends MHD down the chunked path for a response that has neither a content reader nor a flat buffer, and it aborts in try_ready_chunked_body(). That is the harness misusing the API rather than a defect worth reporting, and leaving it in would bury every real finding under the same false report. MHD_RF_INSANITY_- HEADER_CONTENT_LENGTH, set through MHD_set_response_options() in decorate_response(), is the sanctioned way to explore that corner.

Definition at line 389 of file fuzz_request.c.

Referenced by decorate_response().

◆ hdr_values

const char* const hdr_values[]
static
Initial value:
= {
"1", "text/plain", "no-cache", "bytes", "keep-alive", "a=b",
"x\r\nInjected: yes", "", "X-Fuzz", "Thu, 01 Jan 1970 00:00:00 GMT",
"\x7f", "mhd", "/", "\"tag\"", "*", "0"
}

Definition at line 397 of file fuzz_request.c.

Referenced by decorate_response().

◆ mem_limit_tbl

const size_t mem_limit_tbl[]
static
Initial value:
= {
0 , 128, 192, 256, 320, 384, 512, 768, 1024, 1400, 1500,
2048, 4096, 32768
}

Definition at line 212 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ min_discipline

int min_discipline = -3
static

Lower bound for MHD_OPTION_CLIENT_DISCIPLINE_LVL, -3 is the full range. On a tree without the patches listed in README section 6, levels below 0 reach two stale mhd_assert()s in get_req_header(); set MHD_FUZZ_MIN_DISCIPLINE=0 there so that the rest of the state space keeps being explored.

Definition at line 226 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ min_discipline_read

int min_discipline_read
static

Definition at line 227 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ min_mem_limit

size_t min_mem_limit
static

Lower bound for MHD_OPTION_CONNECTION_MEMORY_LIMIT. 0 (the default) fuzzes the full range, including the 128 byte pools that are needed to reach the read-buffer "shift back" code. On a tree without the patches listed in README section 6, pools below ~500 bytes combined with a chunked request body reach two mhd_assert()s in connection.c; set MHD_FUZZ_MIN_MEM_LIMIT=512 there.

Definition at line 246 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ min_mem_limit_read

int min_mem_limit_read
static

Definition at line 247 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ nnc_tbl

const unsigned int nnc_tbl[] = { 4, 1, 8, 64 }
static

Definition at line 256 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ nonce_len

size_t nonce_len
static

Definition at line 177 of file fuzz_request.c.

Referenced by drain_and_harvest(), expand_nonce(), and LLVMFuzzerTestOneInput().

◆ nonce_val

char nonce_val[192]
static

Most recent nonce harvested from a 401 response.

Definition at line 176 of file fuzz_request.c.

Referenced by drain_and_harvest(), expand_nonce(), and LLVMFuzzerTestOneInput().

◆ oracle_on

int oracle_on
static

Definition at line 182 of file fuzz_request.c.

Referenced by ahc(), LLVMFuzzerTestOneInput(), and oracle_check_body().

◆ pending_resume

struct MHD_Connection* pending_resume[MAX_CONNECTIONS]
static

Connections suspended by suspend mode 2, which the pump loop still has to resume. An entry is cleared by completed_cb() so that a connection MHD has finished with is never resumed afterwards.

This has to be a set, not a single slot: op 3 opens a fresh connection without waiting for the previous one to finish, so several connections can be parked at the same time. A single slot silently loses all but the last, and the forgotten one is still suspended when the iteration calls MHD_stop_daemon(), which answers with MHD_PANIC ("MHD_stop_daemon() called while we have suspended connections") – a harness bug that looks exactly like an MHD bug.

Definition at line 292 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput(), pending_resume_add(), pending_resume_drop(), and pending_resume_flush().

◆ resp_body

const char resp_body[] = "hello world, this is the response body"
static

Body used by the buffer / fd / pipe / iovec response kinds.

Definition at line 366 of file fuzz_request.c.

Referenced by make_memfd(), make_pipe_fd(), and make_response().

◆ resp_buf

char resp_buf[RESP_BUF_SIZE]
static

Bytes received from the daemon during the current iteration.

Definition at line 209 of file fuzz_request.c.

Referenced by drain_and_harvest(), and LLVMFuzzerTestOneInput().

◆ resp_len

size_t resp_len
static

Definition at line 210 of file fuzz_request.c.

Referenced by drain_and_harvest(), and LLVMFuzzerTestOneInput().

◆ seed_render_buf

uint8_t seed_render_buf[4096]
static

Definition at line 3019 of file fuzz_request.c.

Referenced by fuzz_seed_get().

◆ seeds

const struct seed_def seeds[]
static

Definition at line 2758 of file fuzz_request.c.

Referenced by fuzz_seed_count(), and fuzz_seed_get().

◆ stat_auth_ok

unsigned long stat_auth_ok
static

Definition at line 190 of file fuzz_request.c.

Referenced by ahc(), and print_stats().

◆ stat_body_bytes

unsigned long stat_body_bytes
static

Definition at line 188 of file fuzz_request.c.

Referenced by ahc(), and print_stats().

◆ stat_challenges

unsigned long stat_challenges
static

Definition at line 189 of file fuzz_request.c.

Referenced by ahc(), and print_stats().

◆ stat_daemons

unsigned long stat_daemons
static

Statistics, printed at exit with –verbose.

Definition at line 185 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput(), and print_stats().

◆ stat_final_calls

unsigned long stat_final_calls
static

Definition at line 187 of file fuzz_request.c.

Referenced by ahc(), and print_stats().

◆ stat_handler_calls

unsigned long stat_handler_calls
static

Definition at line 186 of file fuzz_request.c.

Referenced by ahc(), and print_stats().

◆ stats_registered

int stats_registered
static

Definition at line 191 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput().

◆ tearing_down

int tearing_down
static

Set once the iteration has stopped feeding the daemon and only wants to drain it. suspend_maybe() then does nothing, which is what makes the flush loop in the teardown provably terminate: without it, resuming a connection lets the handler run and park it again, for as many rounds as there are pipelined requests still buffered.

Definition at line 301 of file fuzz_request.c.

Referenced by LLVMFuzzerTestOneInput(), and suspend_maybe().