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