libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

test_get_args.c (13429B)


      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 /**
     22  * @file testzzuf/test_get_args.c
     23  * @brief  Fuzzing testcase for GET requests with hostile query strings
     24  * @author Christian Grothoff
     25  *
     26  * The point of this test is the combination of
     27  * - query strings that contain arguments *without* the '=' character,
     28  *   empty names, empty values, trailing '&', percent-encoded names and
     29  *   a very long tail argument, and
     30  * - a very small connection memory pool (below MHD_BUF_INC_SIZE == 1500).
     31  *
     32  * Only with a pool that small does MHD try to re-use the tail of the
     33  * request header buffer ("shift back", see get_req_headers() in
     34  * connection.c), and the position of the end of the *last parsed element*
     35  * is used for the pointer arithmetic there.  For an argument without '='
     36  * that element has no value at all, so the "end of the value" is not a
     37  * valid pointer.
     38  *
     39  * libcurl always sends at least a "Host:" header, so with libcurl alone
     40  * the last parsed element is always a header and never a query argument.
     41  * Therefore this test additionally uses the raw socket client to send
     42  * header-less HTTP/1.0 requests, for which the last parsed element really
     43  * is the trailing query argument.
     44  */
     45 
     46 #include "platform.h"
     47 #include <curl/curl.h>
     48 #include <microhttpd.h>
     49 #include <stdlib.h>
     50 #include <string.h>
     51 
     52 #include "mhd_zzuf_common.h"
     53 
     54 #define TEST_MAGIC_MARKER 0xFEE1C0DE
     55 
     56 #define EMPTY_PAGE "Empty page."
     57 
     58 /**
     59  * The port offset used if the port cannot be auto-detected.
     60  */
     61 #define TEST_PORT_OFFSET 140
     62 
     63 /**
     64  * The length of the "very long" trailing argument.
     65  * Deliberately larger than the largest memory pool of the option matrix,
     66  * so that MHD has to reject some of the requests as "too large" as well.
     67  */
     68 #define LONG_ARG_LEN 700
     69 
     70 
     71 /**
     72  * The closure of the access handler callback.
     73  */
     74 struct ahc_param
     75 {
     76   /**
     77    * Must have #TEST_MAGIC_MARKER value.
     78    */
     79   unsigned int magic;
     80 
     81   /**
     82    * Non-zero if any error has been encountered.
     83    */
     84   unsigned int err_flag;
     85 
     86   /**
     87    * The checksum over all seen arguments, only used to make sure that
     88    * the compiler cannot optimise the memory accesses away.
     89    */
     90   unsigned int arg_sum;
     91 };
     92 
     93 
     94 /**
     95  * The query strings used by the libcurl-driven part of the test.
     96  * Every one of them ends with an element that has no '=' character.
     97  */
     98 static const char *const query_strings[] = {
     99   "/test_uri?a=1&b&c=&d",
    100   "/test_uri?x&y=&z=%20%41&trail",
    101   "/test_uri?%61%62=%63&noeq",
    102   "/test_uri?one=1&&two=2&",
    103   "/test_uri?=novalue&=&x",
    104   "/test_uri?a=1;b=2&c",
    105   "/test_uri?%zz=%2&broken%",
    106   "/test_uri?dup=1&dup=2&dup"
    107 };
    108 
    109 /**
    110  * The raw (header-less) requests.  Filled in by init_raw_requests().
    111  */
    112 static char *raw_requests[6];
    113 
    114 
    115 /**
    116  * Build the raw requests used by this test.
    117  *
    118  * @return non-zero on success, zero on failure
    119  */
    120 static int
    121 init_raw_requests (void)
    122 {
    123   static const char *const tails[] = {
    124     /* Last element of the request has no '=': the interesting case. */
    125     "/test_uri?a=1&b HTTP/1.0\r\n\r\n",
    126     "/test_uri?trailing_argument_without_equals_sign HTTP/1.0\r\n\r\n",
    127     "/test_uri?a=1&b=2& HTTP/1.0\r\n\r\n",
    128     "/test_uri?= HTTP/1.0\r\n\r\n"
    129   };
    130   size_t i;
    131   char *buf;
    132 
    133   for (i = 0; i < sizeof(tails) / sizeof(tails[0]); ++i)
    134   {
    135     const size_t len = strlen ("GET ") + strlen (tails[i]);
    136     buf = malloc (len + 1);
    137     if (NULL == buf)
    138       return 0;
    139     memcpy (buf, "GET ", strlen ("GET "));
    140     memcpy (buf + strlen ("GET "), tails[i], strlen (tails[i]) + 1);
    141     raw_requests[i] = buf;
    142   }
    143   /* A header-less request with a very long trailing argument without '=' */
    144   buf = malloc (LONG_ARG_LEN + 64);
    145   if (NULL == buf)
    146     return 0;
    147   memcpy (buf, "GET /t?k=v&", strlen ("GET /t?k=v&"));
    148   memset (buf + strlen ("GET /t?k=v&"), 'L', LONG_ARG_LEN);
    149   memcpy (buf + strlen ("GET /t?k=v&") + LONG_ARG_LEN,
    150           " HTTP/1.0\r\n\r\n",
    151           strlen (" HTTP/1.0\r\n\r\n") + 1);
    152   raw_requests[4] = buf;
    153   /* The same, but with a single (minimal) header line, so that the last
    154      parsed element is a header and the "normal" branch is taken with the
    155      very same (small) memory pool. */
    156   buf = malloc (LONG_ARG_LEN + 96);
    157   if (NULL == buf)
    158     return 0;
    159   memcpy (buf, "GET /t?k=v&", strlen ("GET /t?k=v&"));
    160   memset (buf + strlen ("GET /t?k=v&"), 'L', LONG_ARG_LEN);
    161   memcpy (buf + strlen ("GET /t?k=v&") + LONG_ARG_LEN,
    162           " HTTP/1.1\r\nHost: a\r\n\r\n",
    163           strlen (" HTTP/1.1\r\nHost: a\r\n\r\n") + 1);
    164   raw_requests[5] = buf;
    165   return ! 0;
    166 }
    167 
    168 
    169 /**
    170  * Free the raw requests.
    171  */
    172 static void
    173 free_raw_requests (void)
    174 {
    175   size_t i;
    176 
    177   for (i = 0; i < sizeof(raw_requests) / sizeof(raw_requests[0]); ++i)
    178   {
    179     if (NULL != raw_requests[i])
    180       free (raw_requests[i]);
    181     raw_requests[i] = NULL;
    182   }
    183 }
    184 
    185 
    186 /**
    187  * Touch every byte of every key and value so that the sanitizers (or
    188  * the operating system) notice pointers into unmapped memory.
    189  *
    190  * @param cls the closure
    191  * @param kind the kind of the value
    192  * @param key the key, never NULL
    193  * @param key_size the size of @a key
    194  * @param value the value, may be NULL for arguments without '='
    195  * @param value_size the size of @a value
    196  * @return #MHD_YES to continue the iteration
    197  */
    198 static enum MHD_Result
    199 arg_iterator (void *cls,
    200               enum MHD_ValueKind kind,
    201               const char *key,
    202               size_t key_size,
    203               const char *value,
    204               size_t value_size)
    205 {
    206   struct ahc_param *param = (struct ahc_param *) cls;
    207   size_t i;
    208 
    209   (void) kind; /* Unused. Mute compiler warning. */
    210   if (TEST_MAGIC_MARKER != param->magic)
    211   {
    212     fprintf (stderr, "The 'param->magic' has wrong value "
    213              "at line %d.\n", (int) __LINE__);
    214     fflush (stderr);
    215     abort ();
    216   }
    217   if ((NULL == key) && (0 != key_size))
    218   {
    219     fprintf (stderr, "The 'key' is NULL while 'key_size' is not zero "
    220              "at line %d.\n", (int) __LINE__);
    221     param->err_flag = 1;
    222     return MHD_YES;
    223   }
    224   if ((NULL == value) && (0 != value_size))
    225   {
    226     fprintf (stderr, "The 'value' is NULL while 'value_size' is not zero "
    227              "at line %d.\n", (int) __LINE__);
    228     param->err_flag = 1;
    229     return MHD_YES;
    230   }
    231   for (i = 0; i < key_size; ++i)
    232     param->arg_sum += (unsigned int) (unsigned char) key[i];
    233   for (i = 0; i < value_size; ++i)
    234     param->arg_sum += (unsigned int) (unsigned char) value[i];
    235   /* Also touch the zero-termination that MHD guarantees. */
    236   if (NULL != key)
    237     param->arg_sum += (unsigned int) (unsigned char) key[key_size];
    238   if (NULL != value)
    239     param->arg_sum += (unsigned int) (unsigned char) value[value_size];
    240   return MHD_YES;
    241 }
    242 
    243 
    244 static enum MHD_Result
    245 ahc_args (void *cls,
    246           struct MHD_Connection *connection,
    247           const char *url,
    248           const char *method,
    249           const char *version,
    250           const char *upload_data,
    251           size_t *upload_data_size,
    252           void **req_cls)
    253 {
    254   static int marker;
    255   struct ahc_param *param = (struct ahc_param *) cls;
    256   struct MHD_Response *response;
    257   enum MHD_Result ret;
    258   const char *val;
    259 
    260   (void) url; (void) method; (void) version; (void) upload_data;
    261 
    262   if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic))
    263   {
    264     fprintf (stderr, "The 'cls' parameter is invalid "
    265              "at line %d.\n", (int) __LINE__);
    266     fflush (stderr);
    267     abort ();
    268   }
    269   if (NULL == *req_cls)
    270   {
    271     *req_cls = &marker;
    272     return MHD_YES;
    273   }
    274   if ((NULL != upload_data_size) && (0 != *upload_data_size))
    275   {
    276     *upload_data_size = 0; /* Discard any body */
    277     return MHD_YES;
    278   }
    279 
    280   /* Walk over all arguments; this is what dereferences the (possibly
    281      broken) pointers stored while the request line was parsed. */
    282   (void) MHD_get_connection_values_n (connection,
    283                                       MHD_GET_ARGUMENT_KIND,
    284                                       &arg_iterator,
    285                                       param);
    286   /* Also walk over the headers, so that a corrupted header list is
    287      detected as well. */
    288   (void) MHD_get_connection_values_n (connection,
    289                                       MHD_HEADER_KIND,
    290                                       &arg_iterator,
    291                                       param);
    292   /* Lookups of both present and absent keys, including a key that is
    293      known to be present without a value. */
    294   val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "b");
    295   if (NULL != val)
    296     param->arg_sum += (unsigned int) strlen (val);
    297   val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "a");
    298   if (NULL != val)
    299     param->arg_sum += (unsigned int) strlen (val);
    300   val = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND,
    301                                      "no_such_argument");
    302   if (NULL != val)
    303     param->arg_sum += (unsigned int) strlen (val);
    304 
    305   response =
    306     MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (EMPTY_PAGE),
    307                                             EMPTY_PAGE);
    308   if (NULL == response)
    309   {
    310     fprintf (stderr, "MHD_create_response_from_buffer_static() failed "
    311              "at line %d.\n", (int) __LINE__);
    312     return MHD_NO; /* External error, do not raise the error flag */
    313   }
    314   ret = MHD_YES;
    315   if (zzuf_use_close || ! zzuf_oneone)
    316     ret = MHD_add_response_header (response,
    317                                    MHD_HTTP_HEADER_CONNECTION,
    318                                    "close");
    319   if (MHD_YES == ret)
    320     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    321   MHD_destroy_response (response);
    322   return ret;
    323 }
    324 
    325 
    326 static unsigned int
    327 client_run (struct MHD_Daemon *d_extern,
    328             uint16_t port,
    329             void *cls)
    330 {
    331   struct ahc_param *param = (struct ahc_param *) cls;
    332   struct zzuf_curl_driver drv;
    333   struct zzuf_curl_sink sink;
    334   unsigned int i;
    335   unsigned int loops;
    336 
    337   loops = zzuf_loop_count ();
    338   if (! zzuf_curl_driver_init (&drv, d_extern))
    339     return 99; /* Not an MHD error */
    340 
    341   for (i = 0; i < loops; ++i)
    342   {
    343     CURL *c;
    344     const unsigned int num_q =
    345       (unsigned int) (sizeof(query_strings) / sizeof(query_strings[0]));
    346     const unsigned int num_r =
    347       (unsigned int) (sizeof(raw_requests) / sizeof(raw_requests[0]));
    348 
    349     fprintf (stderr, ".");
    350     c = zzuf_curl_setup (port, query_strings[i % num_q], &sink);
    351     if (NULL == c)
    352     {
    353       zzuf_curl_driver_deinit (&drv);
    354       return 99; /* Not an MHD error */
    355     }
    356     zzuf_curl_driver_perform (&drv, c);
    357     curl_easy_cleanup (c);
    358 
    359     /* The raw, header-less requests: only these can make a query
    360        argument the last parsed element of the request header. */
    361     if (ZZUF_RAW_SETUP_FAILED ==
    362         zzuf_raw_request (d_extern, port, raw_requests[i % num_r]))
    363     {
    364       fprintf (stderr, "The raw client could not be set up "
    365                "at line %d.\n", (int) __LINE__);
    366       zzuf_curl_driver_deinit (&drv);
    367       return 99; /* Not an MHD error */
    368     }
    369     fflush (stderr);
    370   }
    371   zzuf_curl_driver_deinit (&drv);
    372 
    373   if (0 != param->err_flag)
    374   {
    375     fprintf (stderr, "One or more errors have been detected by the access "
    376              "handler callback function. At line %d.\n", (int) __LINE__);
    377     return 1;
    378   }
    379   return 0;
    380 }
    381 
    382 
    383 int
    384 main (int argc, char *const *argv)
    385 {
    386   struct ahc_param param;
    387   struct zzuf_run_params rp;
    388   unsigned int res;
    389   int use_magic_exit_codes;
    390 
    391   zzuf_parse_common_args (argc, argv);
    392   use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run;
    393 
    394   res = zzuf_check_runnable ();
    395   if (0 != res)
    396     return use_magic_exit_codes ? (int) res : 0;
    397 
    398   if (CURLE_OK != curl_global_init (CURL_GLOBAL_WIN32))
    399   {
    400     fprintf (stderr, "curl_global_init() failed at line %d.\n",
    401              (int) __LINE__);
    402     return use_magic_exit_codes ? 99 : 0;
    403   }
    404   memset (raw_requests, 0, sizeof(raw_requests));
    405   if (! init_raw_requests ())
    406   {
    407     fprintf (stderr, "malloc() failed at line %d.\n", (int) __LINE__);
    408     free_raw_requests ();
    409     curl_global_cleanup ();
    410     return use_magic_exit_codes ? 99 : 0;
    411   }
    412 
    413   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    414   param.err_flag = 0;
    415   param.arg_sum = 0;
    416 
    417   memset (&rp, 0, sizeof(rp));
    418   rp.port = zzuf_pick_port (TEST_PORT_OFFSET);
    419   rp.ahc = &ahc_args;
    420   rp.ahc_cls = &param;
    421   rp.rcc = NULL;
    422   rp.rcc_cls = NULL;
    423   rp.mem_limit_override = 0; /* Use the small pools of the option matrix */
    424   rp.sweep_profiles = 1;
    425   rp.profile_start = 1;      /* Skip the "plain" (large pool) profile */
    426   rp.client = &client_run;
    427   rp.client_cls = &param;
    428 
    429   res = zzuf_run_polling_modes (&rp);
    430 
    431   free_raw_requests ();
    432   curl_global_cleanup ();
    433 
    434   if (99 == res)
    435     return use_magic_exit_codes ? 99 : 0;
    436   if (77 == res)
    437     return use_magic_exit_codes ? 77 : 0;
    438   return (0 == res) ? 0 : 1; /* 0 == pass */
    439 }