libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 25284c72606b8444e030177a728897ae16681613
parent 72e0245ba97efbadf7d7c3689df4b405e442df0e
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 23 Dec 2025 21:09:11 +0100

port zzuf tests to MHD2 framework

Diffstat:
Msrc/tests/client_server/Makefile.am | 4+++-
Asrc/tests/client_server/README | 39+++++++++++++++++++++++++++++++++++++++
Msrc/tests/client_server/libtest.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Msrc/tests/client_server/libtest_convenience.c | 39++++++++++++++++++++++++++++++++++++---
Msrc/tests/client_server/libtest_convenience_client_request.c | 32++++++++++++++++++++++++++------
Asrc/tests/client_server/zzuf_test_runner.sh | 46++++++++++++++++++++++++++++++++++++++++++++++
6 files changed, 242 insertions(+), 38 deletions(-)

diff --git a/src/tests/client_server/Makefile.am b/src/tests/client_server/Makefile.am @@ -67,4 +67,6 @@ EXTRA_DIST = \ data/test-server-key.pem \ data/check_certs.sh \ data/make_chain.sh \ - data/chain.crt + data/chain.crt \ + README \ + zzuf_test_runner.sh diff --git a/src/tests/client_server/README b/src/tests/client_server/README @@ -0,0 +1,39 @@ +Introduction +============ + +This README describes how to prepare MHD2 for fuzzing +with "zzuf". + +Preparation +=========== + +To run the fuzzer tests, you first need to install "zzuf". +You may furthermore need to build MHD using: + + ./configure OTHER_OPTIONS mhd_cv_have_func_accept4=no + +to ensure MHD2 does not use "accept4()" which is not supported +by zzuf. Naturally, the OTHER_OPTIONS should also be set to catch +as many errors as possible, such as --enable-build-type=release-hardened. +Note that --enable-build-type=debug does not work as ASAN and zzuf +both want to preload libraries and step on each others toes. + +Fuzzing +======= + +You should now be able to run "zzuf_test_runner.sh" manually. +This may take a bit. + +Note that the console output will contain a ton of error messages +as MHD logging is not enabled and fuzzing naturally creates tons of +malformed requests. That is expected! + +Options +======= + +You may + +$ export MHD_TEST_FUZZING=$ROUNDS + +to change the number of rounds of fuzzing. Set "ZZUF_FLAGS" to +pass additional options (like a new seed) to zzuf. diff --git a/src/tests/client_server/libtest.c b/src/tests/client_server/libtest.c @@ -209,6 +209,11 @@ struct ServerContext struct MHD_Daemon *d; /** + * How many rounds do we run the test? + */ + unsigned int rounds; + + /** * Signal for server termination. */ int finsig; @@ -462,16 +467,23 @@ static void * server_phase_logic (void *cls) { struct ServerContext *ctx = cls; + const struct MHDT_Phase *phases = ctx->phase; unsigned int i; + unsigned int j; - for (i = 0; NULL != ctx->phase->label; i++) + for (j = 0; j < ctx->rounds; j++) { fprintf (stderr, - "Running server phase '%s'\n", - ctx->phase->label); - semaphore_down (&ctx->client_sem); - ctx->phase++; - semaphore_up (&ctx->server_sem); + "Running server test round #%u/%u\n", + j + 1, + ctx->rounds); + ctx->phase = &phases[0]; + for (i = 0; NULL != ctx->phase->label; i++) + { + semaphore_down (&ctx->client_sem); + ctx->phase++; + semaphore_up (&ctx->server_sem); + } } fprintf (stderr, "Server terminating\n"); @@ -507,7 +519,8 @@ MHDT_test (MHDT_ServerSetup ss_cb, struct ServerContext ctx = { .run_cb = run_cb, .run_cb_cls = run_cb_cls, - .phase = &phases[0] + .phase = &phases[0], + .rounds = 1 }; struct MHD_Daemon *d; int res; @@ -519,8 +532,25 @@ MHDT_test (MHDT_ServerSetup ss_cb, char base_http_url[128]; char base_https_url[128]; unsigned int i; + unsigned int j; int p[2]; + bool fuzzing = false; + + { + char *rstr = getenv ("MHD_TEST_FUZZING"); + if (NULL != rstr) + { + char dummy; + + if (1 == + sscanf (rstr, + "%u%c", + &ctx.rounds, + &dummy)) + fuzzing = true; + } + } make_pipe (p); semaphore_create (&ctx.server_sem, 0); @@ -561,23 +591,48 @@ MHDT_test (MHDT_ServerSetup ss_cb, { union MHD_DaemonInfoFixedData info; enum MHD_StatusCode sc; + const char *portenv; + uint16_t port; sc = MHD_daemon_get_info_fixed ( d, MHD_DAEMON_INFO_FIXED_BIND_PORT, &info); test_check (MHD_SC_OK == sc); + port = info.v_bind_port_uint16; + + portenv = getenv ("MHD_TEST_FORCE_CLIENT_PORT"); + if (NULL != portenv) + { + unsigned int pn; + char dummy; + + if ( (1 != sscanf (portenv, + "%u%c", + &pn, + &dummy)) || + (pn > 65535) ) + { + fprintf (stderr, + "Invalid port number specified in MHD_TEST_FORCE_CLIENT_PORT"); + MHD_daemon_destroy (d); + return 1; + } + port = (uint16_t) pn; + } + snprintf (base_http_url, sizeof (base_http_url), "http://localhost:%u/", - (unsigned int) info.v_bind_port_uint16); + (unsigned int) port); snprintf (base_https_url, sizeof (base_https_url), "https://localhost:%u/", - (unsigned int) info.v_bind_port_uint16); + (unsigned int) port); pc_http.base_url = base_http_url; pc_https.base_url = base_https_url; } + if (0 != pthread_create (&server_phase_thr, NULL, &server_phase_logic, @@ -602,29 +657,37 @@ MHDT_test (MHDT_ServerSetup ss_cb, MHD_daemon_destroy (d); return 77; } - for (i = 0; NULL != phases[i].label; i++) + for (j = 0; j < ctx.rounds; j++) { - struct MHDT_Phase *pi = &phases[i]; - struct MHDT_PhaseContext *pc - = pi->use_tls - ? &pc_https - : &pc_http; - pc->phase = &phases[i]; - pc->hosts = NULL; fprintf (stderr, - "Running test phase '%s'\n", - pi->label); - if (! run_client_phase (pi, - pc)) + "Running client test round #%u/%u\n", + j + 1, + ctx.rounds); + for (i = 0; NULL != phases[i].label; i++) { - res = 1; - goto cleanup; + struct MHDT_Phase *pi = &phases[i]; + struct MHDT_PhaseContext *pc + = pi->use_tls + ? &pc_https + : &pc_http; + pc->phase = &phases[i]; + pc->hosts = NULL; + fprintf (stderr, + "Running test phase '%s'\n", + pi->label); + if ( (! run_client_phase (pi, + pc)) && + (! fuzzing) ) + { + res = 1; + goto cleanup; + } + pc->hosts = NULL; + /* client is done with phase */ + semaphore_up (&ctx.client_sem); + /* wait for server to have moved to new phase */ + semaphore_down (&ctx.server_sem); } - pc->hosts = NULL; - /* client is done with phase */ - semaphore_up (&ctx.client_sem); - /* wait for server to have moved to new phase */ - semaphore_down (&ctx.server_sem); } res = 0; cleanup: @@ -651,6 +714,7 @@ cleanup: pthread_join (server_phase_thr, &pres)); } + MHD_daemon_destroy (d); semaphore_destroy (&ctx.client_sem); semaphore_destroy (&ctx.server_sem); diff --git a/src/tests/client_server/libtest_convenience.c b/src/tests/client_server/libtest_convenience.c @@ -53,6 +53,37 @@ #include <curl/curl.h> +/** + * Return the port to bind to. Usually zero (any), but can be + * forced to a particular port via environment variables. + */ +static uint16_t +get_port (void) +{ + const char *portenv; + + portenv = getenv ("MHD_TEST_FORCE_SERVER_PORT"); + if (NULL != portenv) + { + unsigned int i; + char dummy; + + if ( (1 != sscanf (portenv, + "%u%c", + &i, + &dummy)) || + (i > 65535) ) + { + fprintf (stderr, + "Invalid port number specified in MHD_TEST_FORCE_PORT, using 0"); + return 0; + } + return (uint16_t) i; + } + return 0; +} + + const char * MHDT_server_setup_minimal (const void *cls, struct MHD_Daemon *d) @@ -68,9 +99,10 @@ MHDT_server_setup_minimal (const void *cls, if (MHD_SC_OK != MHD_DAEMON_SET_OPTIONS ( d, + MHD_D_OPTION_DEFAULT_TIMEOUT (1), MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, - 0))) - return "Failed to bind to port 0!"; + get_port ()))) + return "Failed to bind to port!"; return NULL; } @@ -392,8 +424,9 @@ MHDT_server_setup_external (const void *cls, if (MHD_SC_OK != MHD_DAEMON_SET_OPTIONS ( d, + MHD_D_OPTION_DEFAULT_TIMEOUT (1), MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, - 0))) + get_port ()))) return "Failed to bind to port 0!"; my_epoll_fd = epoll_create1 (0); diff --git a/src/tests/client_server/libtest_convenience_client_request.c b/src/tests/client_server/libtest_convenience_client_request.c @@ -265,13 +265,33 @@ set_url (CURL *c, curl_easy_cleanup (c); return "Failed to set URL"; } - if (CURLE_OK != - curl_easy_setopt (c, - CURLOPT_VERBOSE, - 1)) + if ( (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_TIMEOUT_MS, + 500)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_CONNECTTIMEOUT_MS, + 50)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, + 250)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_FRESH_CONNECT, + 1L)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_FORBID_REUSE, + 1L)) || + (CURLE_OK != + curl_easy_setopt (c, + CURLOPT_VERBOSE, + 0)) ) { curl_easy_cleanup (c); - return "Failed to set verbosity"; + return "Failed to set curl options"; } { /* Force curl to do the request to 127.0.0.1 regardless of @@ -615,7 +635,7 @@ MHDT_client_expect_header (const void *cls, CURLH_HEADER, -1 /* last request */, &hout); - if (CURLHE_BADINDEX == rval) + if (CURLHE_OK != rval) break; found = (0 == strcmp (value, hout->value)); diff --git a/src/tests/client_server/zzuf_test_runner.sh b/src/tests/client_server/zzuf_test_runner.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# This file is in the public domain. + +# Die on failures, including in particular zzuf finding a crash. +set -eu + +# Our settings, passed into the driver(s) via environment variables. +# We need to force the port so that zzuf knows what to grab. +export MHD_TEST_FORCE_SERVER_PORT=${MHD_TEST_FORCE_SERVER_PORT:-4010} +export MHD_TEST_FORCE_CLIENT_PORT=${MHD_TEST_FORCE_CLIENT_PORT:-4010} + +# This sets the number of iterations of the regular tests we run with +# the fuzzer. Using a higher value than 16 could theoretically yield +# better coverage. +export MHD_TEST_FUZZING=${MHD_TEST_FUZZING:-16} + +# List of tests to fuzz. Note that TLS-based tests do not fuzz well... +TESTS="test_client_server test_authentication test_postparser" + +if ! command -v "${ZZUF:-zzuf}" > /dev/null 2>&1 ; +then + echo "zzuf command missing" 1>&2 + exit 77 +fi + +for TEST in $TESTS +do + make $TEST + ${ZZUF:-zzuf} \ + --ratio=0.001:0.4 \ + --autoinc \ + --verbose \ + --signal \ + --ports=${MHD_TEST_FORCE_SERVER_PORT} \ + --max-usertime=${MAX_RUNTIME_SEC:-1800} \ + --network \ + --exclude=. \ + --jobs=1 \ + ${ZZUF_FLAGS:-} \ + ./.libs/$TEST +done +echo "" +echo "****************" +echo "ALL TESTS PASSED" +echo "****************" +exit 0