libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

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 }