test_rq_shift_back.c (19706B)
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 * @file test_rq_shift_back.c 22 * @brief Regression test for the read buffer "shift back" underflow that 23 * happened when the last element of the request headers list was 24 * a query argument without the '=' character. 25 * 26 * The "shift back" optimisation at the end of get_req_headers() re-uses the 27 * tail of the parsed request header area for the read buffer. It computes 28 * the end of the last element of the @a headers_received list as 29 * "value + value_size". A query argument given without '=' is stored with 30 * a NULL @a value, so the computed end was NULL, the resulting shift size 31 * was huge and the read buffer became a wild pointer. 32 * 33 * The optimisation is performed only when the space in the connection 34 * memory pool is small (MHD_BUF_INC_SIZE > read_buffer_size), therefore the 35 * daemons are started with a small MHD_OPTION_CONNECTION_MEMORY_LIMIT. 36 * The tail of the headers list is a query argument only when the request has 37 * no header lines at all, as the query arguments are added while the request 38 * line is parsed and the header fields are appended afterwards. 39 * 40 * The test honours the daemon option matrix of mhd_opt_matrix.h: when one of 41 * the MHD_TEST_* environment variables selects a profile, the built-in sweep 42 * of connection memory limits is replaced by the memory limit of the profile 43 * and the threading/polling mode of the profile is used as well. Without 44 * those variables nothing changes. As the sub-cases need a pool that can 45 * still hold the request, a profile asking for less than 46 * #MIN_POOL_FOR_TEST bytes is raised to that value and the adjustment is 47 * reported. 48 * 49 * @author Christian Grothoff 50 */ 51 #include "MHD_config.h" 52 #include "platform.h" 53 #include <microhttpd.h> 54 #include <stdlib.h> 55 #include <string.h> 56 #include <stdio.h> 57 #include <errno.h> 58 59 #ifdef HAVE_STRINGS_H 60 #include <strings.h> 61 #endif /* HAVE_STRINGS_H */ 62 63 #ifndef WINDOWS 64 #include <unistd.h> 65 #endif /* ! WINDOWS */ 66 67 #ifdef HAVE_SIGNAL_H 68 #include <signal.h> 69 #endif /* HAVE_SIGNAL_H */ 70 71 #include "mhd_sockets.h" /* only macros used */ 72 #include "mhd_opt_matrix.h" 73 /* Turn any MHD_PANIC() or failing mhd_assert() reached from this 74 test into a marked, classifiable test error (TESTING.md, P5). */ 75 #include "mhd_panic_tripwire.h" 76 77 #ifndef MHD_STATICSTR_LEN_ 78 /** 79 * Determine length of static string / macro strings at compile time. 80 */ 81 #define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1) 82 #endif /* ! MHD_STATICSTR_LEN_ */ 83 84 /** 85 * The smallest connection memory pool this test can work with: below that 86 * MHD cannot even build the reply header and the sub-cases would fail for a 87 * reason that has nothing to do with the "shift back" code. 88 */ 89 #define MIN_POOL_FOR_TEST 512 90 91 #if MHD_VERSION >= 0x00097701 92 #define TEST_MK_RESPONSE(len,str) \ 93 MHD_create_response_from_buffer_static ((len), (str)) 94 #else /* MHD_VERSION < 0x00097701 */ 95 #define TEST_MK_RESPONSE(len,str) \ 96 MHD_create_response_from_buffer ((len), (void *) (str), \ 97 MHD_RESPMEM_PERSISTENT) 98 #endif /* MHD_VERSION < 0x00097701 */ 99 100 /** 101 * The exit code for a failed test. 102 */ 103 #define EXIT_FAILED_TEST 1 104 105 /** 106 * The exit code for a failure of the test framework itself. 107 */ 108 #define EXIT_HARD_ERROR 99 109 110 /** 111 * The maximum time (in seconds) to wait for any single socket operation. 112 */ 113 #define TEST_SOCKET_TIMEOUT_SEC 5 114 115 /** 116 * The body of the response sent by the test daemon. 117 */ 118 #define RESP_BODY "OK" 119 120 /** 121 * The expected start of the response. 122 */ 123 #define EXPECTED_STATUS "HTTP/1.1 200" 124 125 /** 126 * Be verbose. 127 */ 128 static int verbose; 129 130 131 _MHD_NORETURN static void 132 hard_error (const char *desc, 133 int line_num) 134 { 135 fprintf (stderr, 136 "HARD ERROR: %s at line %d. Last errno: %d (%s).\n", 137 desc, 138 line_num, 139 (int) errno, 140 strerror (errno)); 141 fflush (stderr); 142 exit (EXIT_HARD_ERROR); 143 } 144 145 146 #define hardErrorExit(desc) hard_error ((desc), __LINE__) 147 148 149 /** 150 * The handler of the requests, always replies with a tiny static response. 151 */ 152 static enum MHD_Result 153 ahc_reply (void *cls, 154 struct MHD_Connection *connection, 155 const char *url, 156 const char *method, 157 const char *version, 158 const char *upload_data, 159 size_t *upload_data_size, 160 void **req_cls) 161 { 162 static int marker; 163 struct MHD_Response *resp; 164 enum MHD_Result ret; 165 166 (void) cls; (void) url; (void) method; (void) version; 167 (void) upload_data; 168 169 if (NULL == *req_cls) 170 { 171 *req_cls = ▮ 172 return MHD_YES; 173 } 174 if (0 != *upload_data_size) 175 { 176 *upload_data_size = 0; 177 return MHD_YES; 178 } 179 resp = TEST_MK_RESPONSE (MHD_STATICSTR_LEN_ (RESP_BODY), 180 RESP_BODY); 181 if (NULL == resp) 182 return MHD_NO; 183 ret = MHD_queue_response (connection, 184 MHD_HTTP_OK, 185 resp); 186 MHD_destroy_response (resp); 187 return ret; 188 } 189 190 191 /** 192 * Set send and receive timeouts on the socket so that the test cannot 193 * block indefinitely. 194 * 195 * @param sk the socket to set the timeouts on 196 * @return non-zero if succeed, zero if failed 197 */ 198 static int 199 set_socket_timeouts (MHD_socket sk) 200 { 201 #if defined(MHD_WINSOCK_SOCKETS) 202 DWORD tv = (DWORD) (TEST_SOCKET_TIMEOUT_SEC * 1000); 203 #else /* ! MHD_WINSOCK_SOCKETS */ 204 struct timeval tv; 205 206 tv.tv_sec = (time_t) TEST_SOCKET_TIMEOUT_SEC; 207 tv.tv_usec = 0; 208 #endif /* ! MHD_WINSOCK_SOCKETS */ 209 if (0 != setsockopt (sk, 210 SOL_SOCKET, 211 SO_RCVTIMEO, 212 (const void *) &tv, 213 (socklen_t) sizeof (tv))) 214 return 0; 215 if (0 != setsockopt (sk, 216 SOL_SOCKET, 217 SO_SNDTIMEO, 218 (const void *) &tv, 219 (socklen_t) sizeof (tv))) 220 return 0; 221 return ! 0; 222 } 223 224 225 /** 226 * Open a TCP connection to the given port on the loopback interface. 227 * 228 * @param port the port to connect to 229 * @return the connected socket, MHD_INVALID_SOCKET on failure 230 */ 231 static MHD_socket 232 connect_to_port (uint16_t port) 233 { 234 MHD_socket sk; 235 struct sockaddr_in sa; 236 237 sk = socket (PF_INET, 238 SOCK_STREAM, 239 0); 240 if (MHD_INVALID_SOCKET == sk) 241 return MHD_INVALID_SOCKET; 242 if (! set_socket_timeouts (sk)) 243 { 244 (void) MHD_socket_close_ (sk); 245 return MHD_INVALID_SOCKET; 246 } 247 memset (&sa, 248 0, 249 sizeof (sa)); 250 sa.sin_family = AF_INET; 251 sa.sin_port = htons (port); 252 sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK); 253 if (0 != connect (sk, 254 (const struct sockaddr *) &sa, 255 (socklen_t) sizeof (sa))) 256 { 257 (void) MHD_socket_close_ (sk); 258 return MHD_INVALID_SOCKET; 259 } 260 return sk; 261 } 262 263 264 /** 265 * Send all the given data over the socket. 266 * 267 * @param sk the socket to use 268 * @param buf the data to send 269 * @param size the size of @a buf 270 * @return non-zero if succeed, zero if failed 271 */ 272 static int 273 send_all (MHD_socket sk, 274 const char *buf, 275 size_t size) 276 { 277 size_t off = 0; 278 279 while (off < size) 280 { 281 ssize_t res; 282 283 res = MHD_send_ (sk, 284 buf + off, 285 size - off); 286 if (0 > res) 287 { 288 if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ())) 289 continue; 290 return 0; 291 } 292 if (0 == res) 293 return 0; 294 off += (size_t) res; 295 } 296 return ! 0; 297 } 298 299 300 /** 301 * Receive data from the socket until @a min_size bytes are received, the 302 * remote side closes the connection or the receive timeout expires. 303 * 304 * @param sk the socket to use 305 * @param[out] buf the buffer to fill 306 * @param buf_size the size of @a buf, the result is always zero-terminated, 307 * therefore at most @a buf_size minus one bytes are received 308 * @param min_size stop receiving as soon as this number of bytes is collected 309 * @return the number of the received bytes 310 */ 311 static size_t 312 recv_reply (MHD_socket sk, 313 char *buf, 314 size_t buf_size, 315 size_t min_size) 316 { 317 size_t off = 0; 318 319 while ((off < min_size) && (off + 1 < buf_size)) 320 { 321 ssize_t res; 322 323 res = MHD_recv_ (sk, 324 buf + off, 325 buf_size - 1 - off); 326 if (0 > res) 327 { 328 if (MHD_SCKT_ERR_IS_EINTR_ (MHD_socket_get_error_ ())) 329 continue; 330 break; /* Timeout or error */ 331 } 332 if (0 == res) 333 break; /* The remote side closed the connection */ 334 off += (size_t) res; 335 } 336 buf[off] = 0; 337 return off; 338 } 339 340 341 /** 342 * The description of a single sub-case. 343 */ 344 struct SubCase 345 { 346 /** 347 * The human readable name of the sub-case. 348 */ 349 const char *name; 350 351 /** 352 * The raw request (and any trailing data) to be sent in a single write. 353 */ 354 const char *request; 355 }; 356 357 358 /** 359 * The sub-cases. 360 * The sub-cases with a trailing query argument without '=' and without any 361 * header line are the ones triggering the bug. The requests with some 362 * trailing data force a non-zero read buffer offset, which turns the 363 * underflow into a memmove() with a wild destination pointer (detected 364 * even when the asserts are disabled), therefore these variants are run 365 * first. The control sub-cases are run before them, so that a failure of 366 * the test harness itself can be distinguished from the bug. 367 */ 368 static const struct SubCase subcases[] = { 369 /* Controls, must always succeed. */ 370 { "no query arguments at all (control)", 371 "GET /a HTTP/1.0\r\n\r\n" }, 372 { "no query arguments at all, with trailing data (control)", 373 "GET /a HTTP/1.0\r\n\r\nZZZZZZZZ" }, 374 { "empty argument value (control)", 375 "GET /a?x= HTTP/1.0\r\n\r\n" }, 376 { "empty argument value, with trailing data (control)", 377 "GET /a?x= HTTP/1.0\r\n\r\nZZZZZZZZ" }, 378 { "non-empty argument value (control)", 379 "GET /a?x=1 HTTP/1.0\r\n\r\n" }, 380 { "non-empty argument value, with trailing data (control)", 381 "GET /a?x=1 HTTP/1.0\r\n\r\nZZZZZZZZ" }, 382 { "argument without '=' plus a header line (control)", 383 "GET /a?x HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" }, 384 { "arguments without '=' plus a header line (control)", 385 "GET /a?x&y HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" }, 386 { "argument with trailing '&' plus a header line (control)", 387 "GET /a?x& HTTP/1.1\r\nHost: h\r\nConnection: close\r\n\r\n" }, 388 /* The sub-cases triggering the bug. */ 389 { "single argument without '=', with trailing data", 390 "GET /a?x HTTP/1.0\r\n\r\nZZZZZZZZ" }, 391 { "single argument without '='", 392 "GET /a?x HTTP/1.0\r\n\r\n" }, 393 { "two arguments, both without '=', with trailing data", 394 "GET /a?x&y HTTP/1.0\r\n\r\nZZZZZZZZ" }, 395 { "two arguments, both without '='", 396 "GET /a?x&y HTTP/1.0\r\n\r\n" }, 397 { "argument with value followed by argument without '=', trailing data", 398 "GET /a?x=1&y HTTP/1.0\r\n\r\nZZZZZZZZ" }, 399 { "argument with value followed by argument without '='", 400 "GET /a?x=1&y HTTP/1.0\r\n\r\n" }, 401 { "single argument with trailing '&', with trailing data", 402 "GET /a?x& HTTP/1.0\r\n\r\nZZZZZZZZ" }, 403 { "single argument with trailing '&'", 404 "GET /a?x& HTTP/1.0\r\n\r\n" }, 405 { "percent-encoded argument name without '=', with trailing data", 406 "GET /a?%78%79 HTTP/1.0\r\n\r\nZZZZZZZZ" }, 407 { "percent-encoded argument name without '='", 408 "GET /a?%78%79 HTTP/1.0\r\n\r\n" }, 409 { "plus-encoded argument name without '=', with trailing data", 410 "GET /a?x+y HTTP/1.0\r\n\r\nZZZZZZZZ" }, 411 { "plus-encoded argument name without '='", 412 "GET /a?x+y HTTP/1.0\r\n\r\n" } 413 }; 414 415 /** 416 * The values for MHD_OPTION_CONNECTION_MEMORY_LIMIT to be tested. 417 * All of them must be small enough to keep the read buffer size below 418 * MHD_BUF_INC_SIZE (1500), otherwise the "shift back" code is not used 419 * at all. 420 */ 421 static const size_t mem_limits[] = { 512, 1024 }; 422 423 /** 424 * The profile selected by the environment, NULL if the built-in sweep is 425 * used (which is the case for a stock "make check"). 426 */ 427 static const struct MHD_OptMatrixProfile *test_prof; 428 429 /** 430 * The (possibly adjusted) copy of the profile @a test_prof points to. 431 */ 432 static struct MHD_OptMatrixProfile test_prof_copy; 433 434 435 /** 436 * Run a single sub-case against the given port. 437 * 438 * @param port the port of the test daemon 439 * @param sc the sub-case to run 440 * @return non-zero if the sub-case succeeded, zero if it failed 441 */ 442 static int 443 run_subcase (uint16_t port, 444 const struct SubCase *sc) 445 { 446 MHD_socket sk; 447 char buf[1024]; 448 size_t got; 449 int ret = ! 0; 450 451 sk = connect_to_port (port); 452 if (MHD_INVALID_SOCKET == sk) 453 hardErrorExit ("Cannot connect to the test daemon"); 454 455 if (! send_all (sk, 456 sc->request, 457 strlen (sc->request))) 458 { 459 fprintf (stderr, 460 "FAILED: cannot send the request of the sub-case '%s'.\n", 461 sc->name); 462 (void) MHD_socket_close_ (sk); 463 return 0; 464 } 465 got = recv_reply (sk, 466 buf, 467 sizeof (buf), 468 MHD_STATICSTR_LEN_ (EXPECTED_STATUS)); 469 if (MHD_STATICSTR_LEN_ (EXPECTED_STATUS) > got) 470 { 471 fprintf (stderr, 472 "FAILED: sub-case '%s': no reply received " 473 "(got %u bytes, expected at least %u bytes). " 474 "The daemon may have crashed.\n", 475 sc->name, 476 (unsigned) got, 477 (unsigned) MHD_STATICSTR_LEN_ (EXPECTED_STATUS)); 478 ret = 0; 479 } 480 else if (0 != memcmp (buf, 481 EXPECTED_STATUS, 482 MHD_STATICSTR_LEN_ (EXPECTED_STATUS))) 483 { 484 fprintf (stderr, 485 "FAILED: sub-case '%s': unexpected reply status. " 486 "Expected '%s', got '%.*s'.\n", 487 sc->name, 488 EXPECTED_STATUS, 489 (int) MHD_STATICSTR_LEN_ (EXPECTED_STATUS), 490 buf); 491 ret = 0; 492 } 493 else if (verbose) 494 printf ("PASSED: %s\n", 495 sc->name); 496 497 (void) MHD_socket_close_ (sk); 498 return ret; 499 } 500 501 502 /** 503 * Run all sub-cases with the given connection memory limit. 504 * 505 * @param mem_limit the value for MHD_OPTION_CONNECTION_MEMORY_LIMIT 506 * @return the number of the failed sub-cases 507 */ 508 static unsigned int 509 run_with_mem_limit (size_t mem_limit) 510 { 511 struct MHD_Daemon *d; 512 const union MHD_DaemonInfo *dinfo; 513 uint16_t port; 514 unsigned int num_failed = 0; 515 size_t i; 516 struct MHD_OptMatrixProfile prof; 517 struct MHD_OptionItem ops[8]; 518 unsigned int flags; 519 520 /* The profile (if any) supplies the discipline level, the threading mode 521 and the polling backend; the memory limit is the one of the sweep, which 522 the caller has already taken from the profile. */ 523 if (NULL != test_prof) 524 { 525 prof = *test_prof; 526 prof.mem_limit = mem_limit; 527 } 528 else 529 { 530 memset (&prof, 0, sizeof (prof)); 531 prof.name = "built-in"; 532 prof.mem_limit = mem_limit; 533 prof.threading = MHD_OPT_MATRIX_THR_INTERNAL; 534 prof.poll_backend = MHD_OPT_MATRIX_POLL_SELECT; 535 } 536 if (0 == mhd_opt_matrix_fill_options (&prof, ops, 537 (unsigned int) (sizeof (ops) 538 / sizeof (ops[0])))) 539 hardErrorExit ("The daemon option array is too small"); 540 /* The client of this test is a plain blocking socket client, so external 541 polling is served with an internal polling thread instead. */ 542 flags = mhd_opt_matrix_daemon_flags (&prof, MHD_USE_ERROR_LOG, 0); 543 544 d = MHD_start_daemon (flags, 545 0, 546 NULL, NULL, 547 &ahc_reply, NULL, 548 MHD_OPTION_ARRAY, ops, 549 MHD_OPTION_CONNECTION_TIMEOUT, 550 (unsigned int) TEST_SOCKET_TIMEOUT_SEC, 551 MHD_OPTION_END); 552 if (NULL == d) 553 hardErrorExit ("Cannot start the test daemon"); 554 555 dinfo = MHD_get_daemon_info (d, 556 MHD_DAEMON_INFO_BIND_PORT); 557 if ((NULL == dinfo) || (0 == dinfo->port)) 558 { 559 MHD_stop_daemon (d); 560 hardErrorExit ("Cannot detect the port used by the test daemon"); 561 } 562 port = (uint16_t) dinfo->port; 563 564 for (i = 0; i < sizeof (subcases) / sizeof (subcases[0]); ++i) 565 { 566 /* Report the sub-case before running it: if the daemon aborts (which is 567 the expected failure mode of the un-fixed code) the whole test process 568 is terminated and the last reported sub-case is the failing one. */ 569 fprintf (stderr, 570 "Running sub-case with memory limit %u: %s\n", 571 (unsigned) mem_limit, 572 subcases[i].name); 573 fflush (stderr); 574 if (! run_subcase (port, 575 subcases + i)) 576 ++num_failed; 577 } 578 MHD_stop_daemon (d); 579 return num_failed; 580 } 581 582 583 int 584 main (int argc, char *const *argv) 585 { 586 unsigned int num_failed = 0; 587 size_t i; 588 589 verbose = 0; 590 for (i = 1; i < (size_t) argc; ++i) 591 { 592 if ((0 == strcmp (argv[i], "-v")) || 593 (0 == strcmp (argv[i], "--verbose"))) 594 verbose = 1; 595 } 596 597 if (MHD_YES != MHD_is_feature_supported (MHD_FEATURE_AUTOSUPPRESS_SIGPIPE)) 598 { 599 #if defined(HAVE_SIGNAL_H) && defined(SIGPIPE) 600 if (SIG_ERR == signal (SIGPIPE, SIG_IGN)) 601 hardErrorExit ("Cannot suppress the SIGPIPE signal"); 602 #else /* ! HAVE_SIGNAL_H || ! SIGPIPE */ 603 fprintf (stderr, 604 "Cannot suppress the SIGPIPE signal.\n"); 605 #endif /* ! HAVE_SIGNAL_H || ! SIGPIPE */ 606 } 607 608 mhd_opt_matrix_print_notice ("test_rq_shift_back"); 609 test_prof = mhd_opt_matrix_from_env (); 610 if (NULL != test_prof) 611 { 612 /* A profile pins one configuration: its memory limit replaces the 613 built-in sweep so that the driver script can walk the matrix. */ 614 char desc[256]; 615 616 test_prof_copy = *test_prof; 617 test_prof = &test_prof_copy; 618 if (! mhd_opt_matrix_profile_supported (test_prof)) 619 { 620 printf ("test_rq_shift_back: the selected profile %s is not supported " 621 "by this build, the test is skipped.\n", 622 mhd_opt_matrix_describe (test_prof, desc, sizeof (desc))); 623 return 77; 624 } 625 if (mhd_opt_matrix_raise_mem_limit (&test_prof_copy, MIN_POOL_FOR_TEST)) 626 printf ("test_rq_shift_back: the connection memory limit of the " 627 "profile was raised to %u, the smallest pool this test can " 628 "work with.\n", (unsigned) MIN_POOL_FOR_TEST); 629 if (0 == test_prof_copy.mem_limit) 630 printf ("test_rq_shift_back: NOTE: the profile uses the default " 631 "connection memory pool, so the \"shift back\" code path is " 632 "not reached at all by this run.\n"); 633 num_failed = run_with_mem_limit (test_prof_copy.mem_limit); 634 } 635 else 636 { 637 for (i = 0; i < sizeof (mem_limits) / sizeof (mem_limits[0]); ++i) 638 num_failed += run_with_mem_limit (mem_limits[i]); 639 } 640 641 if (0 != num_failed) 642 { 643 fprintf (stderr, 644 "FAILED: %u sub-case(s) failed.\n", 645 num_failed); 646 return EXIT_FAILED_TEST; 647 } 648 if (verbose) 649 printf ("All sub-cases passed.\n"); 650 return 0; 651 }