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:
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