test_oom.c (17341B)
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 *flag = 1; 176 177 return MHD_upload_action_from_response ( 178 request, 179 MHD_response_from_callback (MHD_HTTP_STATUS_OK, 180 MHD_SIZE_UNKNOWN, 181 &dyn_cc, 182 flag, 183 &dyn_cc_free)); 184 } 185 186 187 /** 188 * A client has requested the given url using the given method 189 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 190 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). 191 * If @a upload_size is not zero and response action is provided by this 192 * callback, then upload will be discarded and the stream (the connection for 193 * HTTP/1.1) will be closed after sending the response. 194 * 195 * @param cls argument given together with the function 196 * pointer when the handler was registered with MHD 197 * @param request the request object 198 * @param path the requested uri (without arguments after "?") 199 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 200 * #MHD_HTTP_METHOD_PUT, etc.) 201 * @param upload_size the size of the message upload content payload, 202 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 203 * final chunk has not been processed yet) 204 * @return action how to proceed, NULL 205 * if the request must be aborted due to a serious 206 * error while handling the request (implies closure 207 * of underling data stream, for HTTP/1.1 it means 208 * socket closure). 209 */ 210 static const struct MHD_Action * 211 server_req_cb (void *cls, 212 struct MHD_Request *MHD_RESTRICT request, 213 const struct MHD_String *MHD_RESTRICT path, 214 enum MHD_HTTP_Method method, 215 uint_fast64_t upload_size) 216 { 217 (void) cls; 218 (void) path; 219 (void) method; 220 (void) upload_size; 221 return MHD_action_process_upload_full (request, 222 upload_size, 223 &upload_cb, 224 NULL); 225 } 226 227 228 /** 229 * Helper function to deal with partial writes. 230 * Fails hard (calls exit() on failures)! 231 * 232 * @param fd where to write to 233 * @param buf what to write 234 * @param buf_size number of bytes in @a buf 235 */ 236 static void 237 write_all (int fd, 238 const void *buf, 239 size_t buf_size) 240 { 241 const char *cbuf = (const char *) buf; 242 size_t off; 243 244 off = 0; 245 while (off < buf_size) 246 { 247 ssize_t ret; 248 249 ret = write (fd, 250 &cbuf[off], 251 buf_size - off); 252 if (ret <= 0) 253 { 254 fprintf (stderr, 255 "Writing %u bytes to %d failed: %s\n", 256 (unsigned int) (buf_size - off), 257 fd, 258 strerror (errno)); 259 exit (1); 260 } 261 off += (size_t) ret; 262 } 263 } 264 265 266 static int 267 run_test (unsigned int url_len, 268 unsigned int query_len, 269 unsigned int header_len, 270 unsigned int cookie_len, 271 unsigned int body_len) 272 { 273 char filler[BUFFER_SIZE + 1]; 274 int s; 275 276 out_of_memory = false; 277 memset (filler, 278 'a', 279 BUFFER_SIZE); 280 filler[BUFFER_SIZE] = '\0'; /* just to be conservative */ 281 s = socket (AF_INET, 282 SOCK_STREAM, 283 0); 284 if (-1 == s) 285 { 286 fprintf (stderr, 287 "socket() failed: %s\n", 288 strerror (errno)); 289 return -1; 290 } 291 292 { 293 struct sockaddr_in sa = { 294 .sin_family = AF_INET, 295 .sin_port = htons (port), 296 }; 297 inet_pton (AF_INET, 298 "127.0.0.1", 299 &sa.sin_addr); 300 if (0 != connect (s, 301 (struct sockaddr *) &sa, 302 sizeof (sa))) 303 { 304 fprintf (stderr, 305 "bind() failed: %s\n", 306 strerror (errno)); 307 close (s); 308 return -1; 309 } 310 } 311 312 { 313 char upload[BUFFER_SIZE * 2]; 314 int iret; 315 316 iret = snprintf (upload, 317 sizeof (upload), 318 "PUT /%.*s?q=%.*s HTTP/1.0\r\n" 319 "Content-Length: %u\r\n" 320 "Key: %.*s\r\n" 321 "Cookie: a=%.*s\r\n\r\n" 322 "%.*s", 323 (int) url_len, 324 filler, 325 (int) query_len, 326 filler, 327 body_len, 328 (int) header_len, 329 filler, 330 (int) cookie_len, 331 filler, 332 (int) body_len, 333 filler); 334 if ( (-1 == iret) || 335 ( ((size_t) iret) > sizeof (upload)) ) 336 { 337 fprintf (stderr, 338 "failed to build request buffer: %d\n", 339 iret); 340 close (s); 341 return -1; 342 } 343 write_all (s, 344 upload, 345 strlen (upload)); 346 } 347 /* read and discard response */ 348 { 349 bool got_data = false; 350 bool nice = false; 351 char dummy[16 * 1024]; 352 int flags = 0; 353 354 while (1) 355 { 356 ssize_t res; 357 358 res = recv (s, 359 &dummy, 360 sizeof (dummy), 361 flags); 362 flags = MSG_DONTWAIT; 363 if (res > 0) 364 { 365 got_data = true; 366 dummy[res] = '\0'; 367 /* FIXME: allow other "too large" responses to also count as 368 'nice' here */ 369 if (NULL != 370 strstr (dummy, 371 "431 Request Header Fields Too Large")) 372 nice = true; 373 } 374 if (res <= 0) 375 break; 376 } 377 if (nice) 378 out_of_memory = true; 379 if (! got_data) 380 { 381 out_of_memory = true; 382 fprintf (stderr, 383 "Response was not nice (%u/%u/%u/%u/%u)\n", 384 url_len, 385 query_len, 386 header_len, 387 cookie_len, 388 body_len); 389 } 390 } 391 close (s); 392 return out_of_memory ? 1 : 0; 393 } 394 395 396 static int 397 test_url (void) 398 { 399 bool oom_hit; 400 401 oom_hit = false; 402 for (unsigned int i = 0; 403 i < BUFFER_SIZE; 404 i += STEP) 405 { 406 int ret; 407 408 ret = run_test (i, 0, 0, 0, 0); 409 if (-1 == ret) 410 { 411 return 1; 412 } 413 if (1 == ret) 414 { 415 oom_hit = true; 416 } 417 if ( (oom_hit) && (1 != ret) ) 418 { 419 fprintf (stderr, 420 "Strange: OOM stopped at %u after being hit earlier (url)?\n", 421 i); 422 } 423 } 424 if (! oom_hit) 425 { 426 fprintf (stderr, 427 "Failed to trigger OOM condition via URL\n"); 428 return 1; 429 } 430 return 0; 431 } 432 433 434 static int 435 test_query (void) 436 { 437 bool oom_hit; 438 439 oom_hit = false; 440 for (unsigned int i = 0; 441 i < BUFFER_SIZE; 442 i += STEP) 443 { 444 int ret; 445 446 ret = run_test (0, i, 0, 0, 0); 447 if (-1 == ret) 448 { 449 return 1; 450 } 451 if (1 == ret) 452 { 453 oom_hit = true; 454 } 455 if ( (oom_hit) && (1 != ret) ) 456 { 457 fprintf (stderr, 458 "Strange: OOM stopped at %u after being hit earlier (query)?\n", 459 i); 460 } 461 } 462 if (! oom_hit) 463 { 464 fprintf (stderr, 465 "Failed to trigger OOM condition via query\n"); 466 return 1; 467 } 468 return 0; 469 } 470 471 472 static int 473 test_header (void) 474 { 475 bool oom_hit; 476 477 oom_hit = false; 478 for (unsigned int i = 0; 479 i < BUFFER_SIZE; 480 i += STEP) 481 { 482 int ret; 483 484 ret = run_test (0, 0, i, 0, 0); 485 if (-1 == ret) 486 { 487 return 1; 488 } 489 if (1 == ret) 490 { 491 oom_hit = true; 492 } 493 if ( (oom_hit) && (1 != ret) ) 494 { 495 fprintf (stderr, 496 "Strange: OOM stopped at %u after being hit earlier (header)?\n", 497 i); 498 } 499 } 500 if (! oom_hit) 501 { 502 fprintf (stderr, 503 "Failed to trigger OOM condition via header\n"); 504 return 1; 505 } 506 return 0; 507 } 508 509 510 static int 511 test_cookie (void) 512 { 513 bool oom_hit; 514 515 oom_hit = false; 516 for (unsigned int i = 0; 517 i < BUFFER_SIZE; 518 i += STEP) 519 { 520 int ret; 521 522 ret = run_test (0, 0, 0, i, 0); 523 if (-1 == ret) 524 { 525 return 1; 526 } 527 if (1 == ret) 528 { 529 oom_hit = true; 530 } 531 if ( (oom_hit) && (1 != ret) ) 532 { 533 fprintf (stderr, 534 "Strange: OOM stopped at %u after being hit earlier (cookie)?\n", 535 i); 536 } 537 } 538 if (! oom_hit) 539 { 540 fprintf (stderr, 541 "Failed to trigger OOM condition via cookie\n"); 542 return 1; 543 } 544 return 0; 545 } 546 547 548 static int 549 test_body (void) 550 { 551 bool oom_hit; 552 553 oom_hit = false; 554 for (unsigned int i = 0; 555 i < BUFFER_SIZE; 556 i += STEP) 557 { 558 int ret; 559 560 ret = run_test (0, 0, 0, 0, i); 561 if (-1 == ret) 562 { 563 return 1; 564 } 565 if (1 == ret) 566 { 567 oom_hit = true; 568 } 569 if ( (oom_hit) && (1 != ret) ) 570 { 571 fprintf (stderr, 572 "Strange: OOM stopped at %u after being hit earlier (body)?\n", 573 i); 574 } 575 } 576 if (! oom_hit) 577 { 578 fprintf (stderr, 579 "Failed to trigger OOM condition via body\n"); 580 return 1; 581 } 582 return 0; 583 } 584 585 586 static int 587 test_mix (void) 588 { 589 bool oom_hit; 590 591 /* mix and match path */ 592 for (unsigned int i = 0; 593 i < BUFFER_SIZE; 594 i += STEP) 595 { 596 int ret; 597 598 ret = run_test (i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1, i / 5 + 1); 599 if (-1 == ret) 600 { 601 return 1; 602 } 603 if (1 == ret) 604 { 605 oom_hit = true; 606 } 607 if ( (oom_hit) && (1 != ret) ) 608 { 609 fprintf (stderr, 610 "Strange: OOM stopped at %u after being hit earlier (mix)?\n", 611 i); 612 } 613 } 614 if (! oom_hit) 615 { 616 fprintf (stderr, 617 "Failed to trigger OOM condition in mix-and-match\n"); 618 return 1; 619 } 620 621 622 return 0; 623 } 624 625 626 static int 627 run_tests (void) 628 { 629 int ret = 0; 630 631 #if 1 632 ret |= test_url (); 633 ret |= test_query (); 634 ret |= test_header (); 635 ret |= test_cookie (); 636 ret |= test_body (); 637 ret |= test_mix (); 638 #endif 639 return ret; 640 } 641 642 643 static void 644 no_log (void *cls, 645 enum MHD_StatusCode sc, 646 const char *fm, 647 va_list ap) 648 { 649 (void) cls; 650 (void) sc; 651 (void) fm; 652 (void) ap; 653 654 /* intentionally empty */ 655 } 656 657 658 int 659 main (void) 660 { 661 struct MHD_Daemon *d; 662 663 d = MHD_daemon_create (&server_req_cb, 664 NULL); 665 if (MHD_SC_OK != 666 MHD_DAEMON_SET_OPTIONS ( 667 d, 668 MHD_D_OPTION_WM_WORKER_THREADS (2), 669 MHD_D_OPTION_LOG_CALLBACK (&no_log, NULL), 670 MHD_D_OPTION_CONN_MEMORY_LIMIT (BUFFER_SIZE), 671 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (1500), 672 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 673 0))) 674 { 675 fprintf (stderr, 676 "Failed to configure daemon!"); 677 return 1; 678 } 679 680 { 681 enum MHD_StatusCode sc; 682 683 sc = MHD_daemon_start (d); 684 if (MHD_SC_OK != sc) 685 { 686 #ifdef FIXME_STATUS_CODE_TO_STRING_NOT_IMPLEMENTED 687 fprintf (stderr, 688 "Failed to start server: %s\n", 689 MHD_status_code_to_string_lazy (sc)); 690 #else 691 fprintf (stderr, 692 "Failed to start server: %u\n", 693 (unsigned int) sc); 694 #endif 695 MHD_daemon_destroy (d); 696 return 1; 697 } 698 } 699 700 { 701 union MHD_DaemonInfoFixedData info; 702 enum MHD_StatusCode sc; 703 704 sc = MHD_daemon_get_info_fixed ( 705 d, 706 MHD_DAEMON_INFO_FIXED_BIND_PORT, 707 &info); 708 if (MHD_SC_OK != sc) 709 { 710 fprintf (stderr, 711 "Failed to determine our port: %u\n", 712 (unsigned int) sc); 713 MHD_daemon_destroy (d); 714 return 1; 715 } 716 port = info.v_bind_port_uint16; 717 } 718 719 { 720 int result; 721 722 result = run_tests (); 723 MHD_daemon_destroy (d); 724 return result; 725 } 726 }