test_oom.c (17405B)
1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2025 Christian Grothoff 5 6 GNU libmicrohttpd is free software; you can redistribute it and/or 7 modify it under the terms of the GNU Lesser General Public 8 License as published by the Free Software Foundation; either 9 version 2.1 of the License, or (at your option) any later version. 10 11 GNU libmicrohttpd is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 Lesser General Public License for more details. 15 16 Alternatively, you can redistribute GNU libmicrohttpd and/or 17 modify it under the terms of the GNU General Public License as 18 published by the Free Software Foundation; either version 2 of 19 the License, or (at your option) any later version, together 20 with the eCos exception, as follows: 21 22 As a special exception, if other files instantiate templates or 23 use macros or inline functions from this file, or you compile this 24 file and link it with other works to produce a work based on this 25 file, this file does not by itself cause the resulting work to be 26 covered by the GNU General Public License. However the source code 27 for this file must still be made available in accordance with 28 section (3) of the GNU General Public License v2. 29 30 This exception does not invalidate any other reasons why a work 31 based on this file might be covered by the GNU General Public 32 License. 33 34 You should have received copies of the GNU Lesser General Public 35 License and the GNU General Public License along with this library; 36 if not, see <https://www.gnu.org/licenses/>. 37 */ 38 39 /** 40 * @file test_oom.c 41 * @brief tests handling of memory pool exhaustion 42 * @author Christian Grothoff 43 */ 44 #include <stdio.h> 45 #include <stdbool.h> 46 #include <errno.h> 47 #include <string.h> 48 #include <stdlib.h> 49 #include <unistd.h> 50 #include <arpa/inet.h> 51 #include <netinet/ip.h> 52 #include "microhttpd2.h" 53 54 55 /** 56 * How big do we make the MHD buffer? Use a small value so we 57 * can trigger OOM in a reasonable amount of time. 58 */ 59 #define BUFFER_SIZE 2048 60 61 /** 62 * What is the step size. Should eventually use 1, but 63 * as long as we get tons of failures, a larger step size 64 * is probably nicer. 65 */ 66 #define STEP 71 67 68 /** 69 * Our port. 70 */ 71 static uint16_t port; 72 73 /** 74 * Set to true once we hit the out-of-memory condition. 75 */ 76 static bool out_of_memory; 77 78 /** 79 * Callback used by libmicrohttpd in order to obtain content. The 80 * callback is to copy at most @a max bytes of content into @a buf or 81 * provide zero-copy data for #MHD_DCC_action_continue_zc(). 82 * 83 * @param dyn_cont_cls closure argument to the callback 84 * @param ctx the context to produce the action to return, 85 * the pointer is only valid until the callback returns 86 * @param pos position in the datastream to access; 87 * note that if a `struct MHD_Response` object is re-used, 88 * it is possible for the same content reader to 89 * be queried multiple times for the same data; 90 * however, if a `struct MHD_Response` is not re-used, 91 * libmicrohttpd guarantees that "pos" will be 92 * the sum of all data sizes provided by this callback 93 * @param[out] buf where to copy the data 94 * @param max maximum number of bytes to copy to @a buf (size of @a buf), 95 if the size of the content of the response is known then size 96 of the buffer is never larger than amount of the content left 97 * @return action to use, 98 * NULL in case of any error (the response will be aborted) 99 */ 100 static const struct MHD_DynamicContentCreatorAction * 101 dyn_cc (void *dyn_cont_cls, 102 struct MHD_DynamicContentCreatorContext *ctx, 103 uint_fast64_t pos, 104 void *buf, 105 size_t max) 106 { 107 int *flag = dyn_cont_cls; 108 struct MHD_NameValueCStr footer = { 109 .name = "Footer", 110 .value = "Value" 111 }; 112 113 if (0 == *flag) 114 return MHD_DCC_action_finish_with_footer (ctx, 115 0, 116 &footer); 117 (*flag) = 0; 118 memset (buf, 119 'a', 120 max); 121 return MHD_DCC_action_continue (ctx, 122 max); 123 } 124 125 126 /** 127 * This method is called by libmicrohttpd when response with dynamic content 128 * is being destroyed. It should be used to free resources associated 129 * with the dynamic content. 130 * 131 * @param[in] free_cls closure 132 * @ingroup response 133 */ 134 static void 135 dyn_cc_free (void *free_cls) 136 { 137 free (free_cls); 138 } 139 140 141 /** 142 * Function to process data uploaded by a client. 143 * 144 * @param upload_cls the argument given together with the function 145 * pointer when the handler was registered with MHD 146 * @param request the request is being processed 147 * @param content_data_size the size of the @a content_data, 148 * zero when all data have been processed 149 * @param[in] content_data the uploaded content data, 150 * may be modified in the callback, 151 * valid only until return from the callback, 152 * NULL when all data have been processed 153 * @return action specifying how to proceed: 154 * #MHD_upload_action_continue() to continue upload (for incremental 155 * upload processing only), 156 * #MHD_upload_action_suspend() to stop reading the upload until 157 * the request is resumed, 158 * #MHD_upload_action_abort_request() to close the socket, 159 * or a response to discard the rest of the upload and transmit 160 * the response 161 * @ingroup action 162 */ 163 static const struct MHD_UploadAction * 164 upload_cb (void *upload_cls, 165 struct MHD_Request *request, 166 size_t content_data_size, 167 void *content_data) 168 { 169 int *flag; 170 171 (void)upload_cls; 172 (void)content_data_size; 173 (void)content_data; 174 flag = malloc (sizeof (int)); 175 if (NULL == flag) 176 { 177 fprintf (stderr, 178 "Failed to allocate memory.\n"); 179 exit (99); 180 } 181 *flag = 1; 182 183 return MHD_upload_action_from_response ( 184 request, 185 MHD_response_from_callback (MHD_HTTP_STATUS_OK, 186 MHD_SIZE_UNKNOWN, 187 &dyn_cc, 188 flag, 189 &dyn_cc_free)); 190 } 191 192 193 /** 194 * A client has requested the given url using the given method 195 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 196 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 197 * If @a upload_size is not zero and response action is provided by this 198 * callback, then upload will be discarded and the stream (the connection for 199 * HTTP/1.1) will be closed after sending the response. 200 * 201 * @param cls argument given together with the function 202 * pointer when the handler was registered with MHD 203 * @param request the request object 204 * @param path the requested uri (without arguments after "?") 205 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 206 * #MHD_HTTP_METHOD_PUT, etc.) 207 * @param upload_size the size of the message upload content payload, 208 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 209 * final chunk has not been processed yet) 210 * @return action how to proceed, NULL 211 * if the request must be aborted due to a serious 212 * error while handling the request (implies closure 213 * of underling data stream, for HTTP/1.1 it means 214 * socket closure). 215 */ 216 static const struct MHD_Action * 217 server_req_cb (void *cls, 218 struct MHD_Request *MHD_RESTRICT request, 219 const struct MHD_String *MHD_RESTRICT path, 220 enum MHD_HTTP_Method method, 221 uint_fast64_t upload_size) 222 { 223 (void)cls; 224 (void)path; 225 (void)method; 226 (void)upload_size; 227 return MHD_action_process_upload_full (request, 228 upload_size, 229 &upload_cb, 230 NULL); 231 } 232 233 234 /** 235 * Helper function to deal with partial writes. 236 * Fails hard (calls exit() on failures)! 237 * 238 * @param fd where to write to 239 * @param buf what to write 240 * @param buf_size number of bytes in @a buf 241 */ 242 static void 243 write_all (int fd, 244 const void *buf, 245 size_t buf_size) 246 { 247 const char *cbuf = (const char *)buf; 248 size_t off; 249 250 off = 0; 251 while (off < buf_size) 252 { 253 ssize_t ret; 254 255 ret = write (fd, 256 &cbuf[off], 257 buf_size - off); 258 if (ret <= 0) 259 { 260 fprintf (stderr, 261 "Writing %u bytes to %d failed: %s\n", 262 (unsigned int)(buf_size - off), 263 fd, 264 strerror (errno)); 265 exit (1); 266 } 267 off += (size_t)ret; 268 } 269 } 270 271 272 static int 273 run_test (unsigned int url_len, 274 unsigned int query_len, 275 unsigned int header_len, 276 unsigned int cookie_len, 277 unsigned int body_len) 278 { 279 char filler[BUFFER_SIZE + 1]; 280 int s; 281 282 out_of_memory = false; 283 memset (filler, 284 'a', 285 BUFFER_SIZE); 286 filler[BUFFER_SIZE] = '\0'; /* just to be conservative */ 287 s = socket (AF_INET, 288 SOCK_STREAM, 289 0); 290 if (-1 == s) 291 { 292 fprintf (stderr, 293 "socket() failed: %s\n", 294 strerror (errno)); 295 return -1; 296 } 297 298 { 299 struct sockaddr_in sa = { 300 .sin_family = AF_INET, 301 .sin_port = htons (port), 302 }; 303 inet_pton (AF_INET, 304 "127.0.0.1", 305 &sa.sin_addr); 306 if (0 != connect (s, 307 (struct sockaddr *)&sa, 308 sizeof (sa))) 309 { 310 fprintf (stderr, 311 "bind() failed: %s\n", 312 strerror (errno)); 313 close (s); 314 return -1; 315 } 316 } 317 318 { 319 char upload[BUFFER_SIZE * 2]; 320 int iret; 321 322 iret = snprintf (upload, 323 sizeof (upload), 324 "PUT /%.*s?q=%.*s HTTP/1.0\r\n" 325 "Content-Length: %u\r\n" 326 "Key: %.*s\r\n" 327 "Cookie: a=%.*s\r\n\r\n" 328 "%.*s", 329 (int)url_len, 330 filler, 331 (int)query_len, 332 filler, 333 body_len, 334 (int)header_len, 335 filler, 336 (int)cookie_len, 337 filler, 338 (int)body_len, 339 filler); 340 if ((-1 == iret) 341 || (((size_t)iret) > sizeof (upload))) 342 { 343 fprintf (stderr, 344 "failed to build request buffer: %d\n", 345 iret); 346 close (s); 347 return -1; 348 } 349 write_all (s, 350 upload, 351 strlen (upload)); 352 } 353 /* read and discard response */ 354 { 355 bool got_data = false; 356 bool nice = false; 357 char dummy[16 * 1024]; 358 int flags = 0; 359 360 while (1) 361 { 362 ssize_t res; 363 364 res = recv (s, 365 &dummy, 366 sizeof (dummy), 367 flags); 368 flags = MSG_DONTWAIT; 369 if (res > 0) 370 { 371 got_data = true; 372 dummy[res] = '\0'; 373 /* FIXME: allow other "too large" responses to also count as 374 'nice' here */ 375 if (NULL != 376 strstr (dummy, 377 "431 Request Header Fields Too Large")) 378 nice = true; 379 } 380 if (res <= 0) 381 break; 382 } 383 if (nice) 384 out_of_memory = true; 385 if (!got_data) 386 { 387 out_of_memory = true; 388 fprintf (stderr, 389 "Response was not nice (%u/%u/%u/%u/%u)\n", 390 url_len, 391 query_len, 392 header_len, 393 cookie_len, 394 body_len); 395 } 396 } 397 close (s); 398 return out_of_memory ? 1 : 0; 399 } 400 401 402 static int 403 test_url (void) 404 { 405 bool oom_hit; 406 407 oom_hit = false; 408 for (unsigned int i = 0; 409 i < BUFFER_SIZE; 410 i += STEP) 411 { 412 int ret; 413 414 ret = run_test (i, 0, 0, 0, 0); 415 if (-1 == ret) 416 { 417 return 1; 418 } 419 if (1 == ret) 420 { 421 oom_hit = true; 422 } 423 if ((oom_hit) && (1 != ret)) 424 { 425 fprintf (stderr, 426 "Strange: OOM stopped at %u after being hit earlier (url)?\n", 427 i); 428 } 429 } 430 if (!oom_hit) 431 { 432 fprintf (stderr, 433 "Failed to trigger OOM condition via URL\n"); 434 return 1; 435 } 436 return 0; 437 } 438 439 440 static int 441 test_query (void) 442 { 443 bool oom_hit; 444 445 oom_hit = false; 446 for (unsigned int i = 0; 447 i < BUFFER_SIZE; 448 i += STEP) 449 { 450 int ret; 451 452 ret = run_test (0, i, 0, 0, 0); 453 if (-1 == ret) 454 { 455 return 1; 456 } 457 if (1 == ret) 458 { 459 oom_hit = true; 460 } 461 if ((oom_hit) && (1 != ret)) 462 { 463 fprintf (stderr, 464 "Strange: OOM stopped at %u after being hit earlier (query)?\n", 465 i); 466 } 467 } 468 if (!oom_hit) 469 { 470 fprintf (stderr, 471 "Failed to trigger OOM condition via query\n"); 472 return 1; 473 } 474 return 0; 475 } 476 477 478 static int 479 test_header (void) 480 { 481 bool oom_hit; 482 483 oom_hit = false; 484 for (unsigned int i = 0; 485 i < BUFFER_SIZE; 486 i += STEP) 487 { 488 int ret; 489 490 ret = run_test (0, 0, i, 0, 0); 491 if (-1 == ret) 492 { 493 return 1; 494 } 495 if (1 == ret) 496 { 497 oom_hit = true; 498 } 499 if ((oom_hit) && (1 != ret)) 500 { 501 fprintf (stderr, 502 "Strange: OOM stopped at %u after being hit earlier (header)?\n", 503 i); 504 } 505 } 506 if (!oom_hit) 507 { 508 fprintf (stderr, 509 "Failed to trigger OOM condition via header\n"); 510 return 1; 511 } 512 return 0; 513 } 514 515 516 static int 517 test_cookie (void) 518 { 519 bool oom_hit; 520 521 oom_hit = false; 522 for (unsigned int i = 0; 523 i < BUFFER_SIZE; 524 i += STEP) 525 { 526 int ret; 527 528 ret = run_test (0, 0, 0, i, 0); 529 if (-1 == ret) 530 { 531 return 1; 532 } 533 if (1 == ret) 534 { 535 oom_hit = true; 536 } 537 if ((oom_hit) && (1 != ret)) 538 { 539 fprintf (stderr, 540 "Strange: OOM stopped at %u after being hit earlier (cookie)?\n", 541 i); 542 } 543 } 544 if (!oom_hit) 545 { 546 fprintf (stderr, 547 "Failed to trigger OOM condition via cookie\n"); 548 return 1; 549 } 550 return 0; 551 } 552 553 554 static int 555 test_body (void) 556 { 557 bool oom_hit; 558 559 oom_hit = false; 560 for (unsigned int i = 0; 561 i < BUFFER_SIZE; 562 i += STEP) 563 { 564 int ret; 565 566 ret = run_test (0, 0, 0, 0, i); 567 if (-1 == ret) 568 { 569 return 1; 570 } 571 if (1 == ret) 572 { 573 oom_hit = true; 574 } 575 if ((oom_hit) && (1 != ret)) 576 { 577 fprintf (stderr, 578 "Strange: OOM stopped at %u after being hit earlier (body)?\n", 579 i); 580 } 581 } 582 if (!oom_hit) 583 { 584 fprintf (stderr, 585 "Failed to trigger OOM condition via body\n"); 586 return 1; 587 } 588 return 0; 589 } 590 591 592 static int 593 test_mix (void) 594 { 595 bool oom_hit; 596 597 /* mix and match path */ 598 for (unsigned int i = 0; 599 i < BUFFER_SIZE; 600 i += STEP) 601 { 602 int ret; 603 604 ret = run_test (i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1); 605 if (-1 == ret) 606 { 607 return 1; 608 } 609 if (1 == ret) 610 { 611 oom_hit = true; 612 } 613 if ((oom_hit) && (1 != ret)) 614 { 615 fprintf (stderr, 616 "Strange: OOM stopped at %u after being hit earlier (mix)?\n", 617 i); 618 } 619 } 620 if (!oom_hit) 621 { 622 fprintf (stderr, 623 "Failed to trigger OOM condition in mix-and-match\n"); 624 return 1; 625 } 626 627 628 return 0; 629 } 630 631 632 static int 633 run_tests (void) 634 { 635 int ret = 0; 636 637 #if 1 638 ret |= test_url (); 639 ret |= test_query (); 640 ret |= test_header (); 641 ret |= test_cookie (); 642 ret |= test_body (); 643 ret |= test_mix (); 644 #endif 645 return ret; 646 } 647 648 649 static void 650 no_log (void *cls, 651 enum MHD_StatusCode sc, 652 const char *fm, 653 va_list ap) 654 { 655 (void)cls; 656 (void)sc; 657 (void)fm; 658 (void)ap; 659 660 /* intentionally empty */ 661 } 662 663 664 int 665 main (void) 666 { 667 struct MHD_Daemon *d; 668 669 d = MHD_daemon_create (&server_req_cb, 670 NULL); 671 if (MHD_SC_OK != 672 MHD_DAEMON_SET_OPTIONS ( 673 d, 674 MHD_D_OPTION_WM_WORKER_THREADS (2), 675 MHD_D_OPTION_LOG_CALLBACK (&no_log, NULL), 676 MHD_D_OPTION_CONN_MEMORY_LIMIT (BUFFER_SIZE), 677 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1500), 678 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 679 0))) 680 { 681 fprintf (stderr, 682 "Failed to configure daemon!"); 683 return 1; 684 } 685 686 { 687 enum MHD_StatusCode sc; 688 689 sc = MHD_daemon_start (d); 690 if (MHD_SC_OK != sc) 691 { 692 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 693 fprintf (stderr, 694 "Failed to start server: %s\n", 695 MHD_status_code_to_string_lazy (sc)); 696 #else 697 fprintf (stderr, 698 "Failed to start server: %u\n", 699 (unsigned int)sc); 700 #endif 701 MHD_daemon_destroy (d); 702 return 1; 703 } 704 } 705 706 { 707 union MHD_DaemonInfoFixedData info; 708 enum MHD_StatusCode sc; 709 710 sc = MHD_daemon_get_info_fixed ( 711 d, 712 MHD_DAEMON_INFO_FIXED_BIND_PORT, 713 &info); 714 if (MHD_SC_OK != sc) 715 { 716 fprintf (stderr, 717 "Failed to determine our port: %u\n", 718 (unsigned int)sc); 719 MHD_daemon_destroy (d); 720 return 1; 721 } 722 port = info.v_bind_port_uint16; 723 } 724 725 { 726 int result; 727 728 result = run_tests (); 729 MHD_daemon_destroy (d); 730 return result; 731 } 732 }