test_get_args.c (13429B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2026 Christian Grothoff 4 5 libmicrohttpd is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 2, or (at your 8 option) any later version. 9 10 libmicrohttpd is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libmicrohttpd; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 21 /** 22 * @file testzzuf/test_get_args.c 23 * @brief Fuzzing testcase for GET requests with hostile query strings 24 * @author Christian Grothoff 25 * 26 * The point of this test is the combination of 27 * - query strings that contain arguments *without* the '=' character, 28 * empty names, empty values, trailing '&', percent-encoded names and 29 * a very long tail argument, and 30 * - a very small connection memory pool (below MHD_BUF_INC_SIZE == 1500). 31 * 32 * Only with a pool that small does MHD try to re-use the tail of the 33 * request header buffer ("shift back", see get_req_headers() in 34 * connection.c), and the position of the end of the *last parsed element* 35 * is used for the pointer arithmetic there. For an argument without '=' 36 * that element has no value at all, so the "end of the value" is not a 37 * valid pointer. 38 * 39 * libcurl always sends at least a "Host:" header, so with libcurl alone 40 * the last parsed element is always a header and never a query argument. 41 * Therefore this test additionally uses the raw socket client to send 42 * header-less HTTP/1.0 requests, for which the last parsed element really 43 * is the trailing query argument. 44 */ 45 46 #include "platform.h" 47 #include <curl/curl.h> 48 #include <microhttpd.h> 49 #include <stdlib.h> 50 #include <string.h> 51 52 #include "mhd_zzuf_common.h" 53 54 #define TEST_MAGIC_MARKER 0xFEE1C0DE 55 56 #define EMPTY_PAGE "Empty page." 57 58 /** 59 * The port offset used if the port cannot be auto-detected. 60 */ 61 #define TEST_PORT_OFFSET 140 62 63 /** 64 * The length of the "very long" trailing argument. 65 * Deliberately larger than the largest memory pool of the option matrix, 66 * so that MHD has to reject some of the requests as "too large" as well. 67 */ 68 #define LONG_ARG_LEN 700 69 70 71 /** 72 * The closure of the access handler callback. 73 */ 74 struct ahc_param 75 { 76 /** 77 * Must have #TEST_MAGIC_MARKER value. 78 */ 79 unsigned int magic; 80 81 /** 82 * Non-zero if any error has been encountered. 83 */ 84 unsigned int err_flag; 85 86 /** 87 * The checksum over all seen arguments, only used to make sure that 88 * the compiler cannot optimise the memory accesses away. 89 */ 90 unsigned int arg_sum; 91 }; 92 93 94 /** 95 * The query strings used by the libcurl-driven part of the test. 96 * Every one of them ends with an element that has no '=' character. 97 */ 98 static const char *const query_strings[] = { 99 "/test_uri?a=1&b&c=&d", 100 "/test_uri?x&y=&z=%20%41&trail", 101 "/test_uri?%61%62=%63&noeq", 102 "/test_uri?one=1&&two=2&", 103 "/test_uri?=novalue&=&x", 104 "/test_uri?a=1;b=2&c", 105 "/test_uri?%zz=%2&broken%", 106 "/test_uri?dup=1&dup=2&dup" 107 }; 108 109 /** 110 * The raw (header-less) requests. Filled in by init_raw_requests(). 111 */ 112 static char *raw_requests[6]; 113 114 115 /** 116 * Build the raw requests used by this test. 117 * 118 * @return non-zero on success, zero on failure 119 */ 120 static int 121 init_raw_requests (void) 122 { 123 static const char *const tails[] = { 124 /* Last element of the request has no '=': the interesting case. */ 125 "/test_uri?a=1&b HTTP/1.0\r\n\r\n", 126 "/test_uri?trailing_argument_without_equals_sign HTTP/1.0\r\n\r\n", 127 "/test_uri?a=1&b=2& HTTP/1.0\r\n\r\n", 128 "/test_uri?= HTTP/1.0\r\n\r\n" 129 }; 130 size_t i; 131 char *buf; 132 133 for (i = 0; i < sizeof(tails) / sizeof(tails[0]); ++i) 134 { 135 const size_t len = strlen ("GET ") + strlen (tails[i]); 136 buf = malloc (len + 1); 137 if (NULL == buf) 138 return 0; 139 memcpy (buf, "GET ", strlen ("GET ")); 140 memcpy (buf + strlen ("GET "), tails[i], strlen (tails[i]) + 1); 141 raw_requests[i] = buf; 142 } 143 /* A header-less request with a very long trailing argument without '=' */ 144 buf = malloc (LONG_ARG_LEN + 64); 145 if (NULL == buf) 146 return 0; 147 memcpy (buf, "GET /t?k=v&", strlen ("GET /t?k=v&")); 148 memset (buf + strlen ("GET /t?k=v&"), 'L', LONG_ARG_LEN); 149 memcpy (buf + strlen ("GET /t?k=v&") + LONG_ARG_LEN, 150 " HTTP/1.0\r\n\r\n", 151 strlen (" HTTP/1.0\r\n\r\n") + 1); 152 raw_requests[4] = buf; 153 /* The same, but with a single (minimal) header line, so that the last 154 parsed element is a header and the "normal" branch is taken with the 155 very same (small) memory pool. */ 156 buf = malloc (LONG_ARG_LEN + 96); 157 if (NULL == buf) 158 return 0; 159 memcpy (buf, "GET /t?k=v&", strlen ("GET /t?k=v&")); 160 memset (buf + strlen ("GET /t?k=v&"), 'L', LONG_ARG_LEN); 161 memcpy (buf + strlen ("GET /t?k=v&") + LONG_ARG_LEN, 162 " HTTP/1.1\r\nHost: a\r\n\r\n", 163 strlen (" HTTP/1.1\r\nHost: a\r\n\r\n") + 1); 164 raw_requests[5] = buf; 165 return ! 0; 166 } 167 168 169 /** 170 * Free the raw requests. 171 */ 172 static void 173 free_raw_requests (void) 174 { 175 size_t i; 176 177 for (i = 0; i < sizeof(raw_requests) / sizeof(raw_requests[0]); ++i) 178 { 179 if (NULL != raw_requests[i]) 180 free (raw_requests[i]); 181 raw_requests[i] = NULL; 182 } 183 } 184 185 186 /** 187 * Touch every byte of every key and value so that the sanitizers (or 188 * the operating system) notice pointers into unmapped memory. 189 * 190 * @param cls the closure 191 * @param kind the kind of the value 192 * @param key the key, never NULL 193 * @param key_size the size of @a key 194 * @param value the value, may be NULL for arguments without '=' 195 * @param value_size the size of @a value 196 * @return #MHD_YES to continue the iteration 197 */ 198 static enum MHD_Result 199 arg_iterator (void *cls, 200 enum MHD_ValueKind kind, 201 const char *key, 202 size_t key_size, 203 const char *value, 204 size_t value_size) 205 { 206 struct ahc_param *param = (struct ahc_param *) cls; 207 size_t i; 208 209 (void) kind; /* Unused. Mute compiler warning. */ 210 if (TEST_MAGIC_MARKER != param->magic) 211 { 212 fprintf (stderr, "The 'param->magic' has wrong value " 213 "at line %d.\n", (int) __LINE__); 214 fflush (stderr); 215 abort (); 216 } 217 if ((NULL == key) && (0 != key_size)) 218 { 219 fprintf (stderr, "The 'key' is NULL while 'key_size' is not zero " 220 "at line %d.\n", (int) __LINE__); 221 param->err_flag = 1; 222 return MHD_YES; 223 } 224 if ((NULL == value) && (0 != value_size)) 225 { 226 fprintf (stderr, "The 'value' is NULL while 'value_size' is not zero " 227 "at line %d.\n", (int) __LINE__); 228 param->err_flag = 1; 229 return MHD_YES; 230 } 231 for (i = 0; i < key_size; ++i) 232 param->arg_sum += (unsigned int) (unsigned char) key[i]; 233 for (i = 0; i < value_size; ++i) 234 param->arg_sum += (unsigned int) (unsigned char) value[i]; 235 /* Also touch the zero-termination that MHD guarantees. */ 236 if (NULL != key) 237 param->arg_sum += (unsigned int) (unsigned char) key[key_size]; 238 if (NULL != value) 239 param->arg_sum += (unsigned int) (unsigned char) value[value_size]; 240 return MHD_YES; 241 } 242 243 244 static enum MHD_Result 245 ahc_args (void *cls, 246 struct MHD_Connection *connection, 247 const char *url, 248 const char *method, 249 const char *version, 250 const char *upload_data, 251 size_t *upload_data_size, 252 void **req_cls) 253 { 254 static int marker; 255 struct ahc_param *param = (struct ahc_param *) cls; 256 struct MHD_Response *response; 257 enum MHD_Result ret; 258 const char *val; 259 260 (void) url; (void) method; (void) version; (void) upload_data; 261 262 if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic)) 263 { 264 fprintf (stderr, "The 'cls' parameter is invalid " 265 "at line %d.\n", (int) __LINE__); 266 fflush (stderr); 267 abort (); 268 } 269 if (NULL == *req_cls) 270 { 271 *req_cls = ▮ 272 return MHD_YES; 273 } 274 if ((NULL != upload_data_size) && (0 != *upload_data_size)) 275 { 276 *upload_data_size = 0; /* Discard any body */ 277 return MHD_YES; 278 } 279 280 /* Walk over all arguments; this is what dereferences the (possibly 281 broken) pointers stored while the request line was parsed. */ 282 (void) MHD_get_connection_values_n (connection, 283 MHD_GET_ARGUMENT_KIND, 284 &arg_iterator, 285 param); 286 /* Also walk over the headers, so that a corrupted header list is 287 detected as well. */ 288 (void) MHD_get_connection_values_n (connection, 289 MHD_HEADER_KIND, 290 &arg_iterator, 291 param); 292 /* Lookups of both present and absent keys, including a key that is 293 known to be present without a value. */ 294 val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "b"); 295 if (NULL != val) 296 param->arg_sum += (unsigned int) strlen (val); 297 val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "a"); 298 if (NULL != val) 299 param->arg_sum += (unsigned int) strlen (val); 300 val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, 301 "no_such_argument"); 302 if (NULL != val) 303 param->arg_sum += (unsigned int) strlen (val); 304 305 response = 306 MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (EMPTY_PAGE), 307 EMPTY_PAGE); 308 if (NULL == response) 309 { 310 fprintf (stderr, "MHD_create_response_from_buffer_static() failed " 311 "at line %d.\n", (int) __LINE__); 312 return MHD_NO; /* External error, do not raise the error flag */ 313 } 314 ret = MHD_YES; 315 if (zzuf_use_close || ! zzuf_oneone) 316 ret = MHD_add_response_header (response, 317 MHD_HTTP_HEADER_CONNECTION, 318 "close"); 319 if (MHD_YES == ret) 320 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 321 MHD_destroy_response (response); 322 return ret; 323 } 324 325 326 static unsigned int 327 client_run (struct MHD_Daemon *d_extern, 328 uint16_t port, 329 void *cls) 330 { 331 struct ahc_param *param = (struct ahc_param *) cls; 332 struct zzuf_curl_driver drv; 333 struct zzuf_curl_sink sink; 334 unsigned int i; 335 unsigned int loops; 336 337 loops = zzuf_loop_count (); 338 if (! zzuf_curl_driver_init (&drv, d_extern)) 339 return 99; /* Not an MHD error */ 340 341 for (i = 0; i < loops; ++i) 342 { 343 CURL *c; 344 const unsigned int num_q = 345 (unsigned int) (sizeof(query_strings) / sizeof(query_strings[0])); 346 const unsigned int num_r = 347 (unsigned int) (sizeof(raw_requests) / sizeof(raw_requests[0])); 348 349 fprintf (stderr, "."); 350 c = zzuf_curl_setup (port, query_strings[i % num_q], &sink); 351 if (NULL == c) 352 { 353 zzuf_curl_driver_deinit (&drv); 354 return 99; /* Not an MHD error */ 355 } 356 zzuf_curl_driver_perform (&drv, c); 357 curl_easy_cleanup (c); 358 359 /* The raw, header-less requests: only these can make a query 360 argument the last parsed element of the request header. */ 361 if (ZZUF_RAW_SETUP_FAILED == 362 zzuf_raw_request (d_extern, port, raw_requests[i % num_r])) 363 { 364 fprintf (stderr, "The raw client could not be set up " 365 "at line %d.\n", (int) __LINE__); 366 zzuf_curl_driver_deinit (&drv); 367 return 99; /* Not an MHD error */ 368 } 369 fflush (stderr); 370 } 371 zzuf_curl_driver_deinit (&drv); 372 373 if (0 != param->err_flag) 374 { 375 fprintf (stderr, "One or more errors have been detected by the access " 376 "handler callback function. At line %d.\n", (int) __LINE__); 377 return 1; 378 } 379 return 0; 380 } 381 382 383 int 384 main (int argc, char *const *argv) 385 { 386 struct ahc_param param; 387 struct zzuf_run_params rp; 388 unsigned int res; 389 int use_magic_exit_codes; 390 391 zzuf_parse_common_args (argc, argv); 392 use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run; 393 394 res = zzuf_check_runnable (); 395 if (0 != res) 396 return use_magic_exit_codes ? (int) res : 0; 397 398 if (CURLE_OK != curl_global_init (CURL_GLOBAL_WIN32)) 399 { 400 fprintf (stderr, "curl_global_init() failed at line %d.\n", 401 (int) __LINE__); 402 return use_magic_exit_codes ? 99 : 0; 403 } 404 memset (raw_requests, 0, sizeof(raw_requests)); 405 if (! init_raw_requests ()) 406 { 407 fprintf (stderr, "malloc() failed at line %d.\n", (int) __LINE__); 408 free_raw_requests (); 409 curl_global_cleanup (); 410 return use_magic_exit_codes ? 99 : 0; 411 } 412 413 param.magic = (unsigned int) TEST_MAGIC_MARKER; 414 param.err_flag = 0; 415 param.arg_sum = 0; 416 417 memset (&rp, 0, sizeof(rp)); 418 rp.port = zzuf_pick_port (TEST_PORT_OFFSET); 419 rp.ahc = &ahc_args; 420 rp.ahc_cls = ¶m; 421 rp.rcc = NULL; 422 rp.rcc_cls = NULL; 423 rp.mem_limit_override = 0; /* Use the small pools of the option matrix */ 424 rp.sweep_profiles = 1; 425 rp.profile_start = 1; /* Skip the "plain" (large pool) profile */ 426 rp.client = &client_run; 427 rp.client_cls = ¶m; 428 429 res = zzuf_run_polling_modes (&rp); 430 431 free_raw_requests (); 432 curl_global_cleanup (); 433 434 if (99 == res) 435 return use_magic_exit_codes ? 99 : 0; 436 if (77 == res) 437 return use_magic_exit_codes ? 77 : 0; 438 return (0 == res) ? 0 : 1; /* 0 == pass */ 439 }