libmicrohttpd

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

test_basic_auth.c (12691B)


      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_basic_auth.c
     23  * @brief  Fuzzing testcase for Basic Authentication
     24  * @author Christian Grothoff
     25  *
     26  * Before this test existed, no fuzzing test of this directory ever sent an
     27  * "Authorization:" header, so neither the Base64 decoder nor the username /
     28  * password splitting of MHD were reachable by the fuzzer at all.
     29  *
     30  * libcurl performs a normal Basic Authentication handshake (so that zzuf's
     31  * bit flips land inside a real "Authorization: Basic" header) and the raw
     32  * socket client adds hand-crafted headers with Base64 payloads that are
     33  * truncated, over-long, contain characters outside the Base64 alphabet,
     34  * decode to data with binary zeros or lack the ':' separator entirely.
     35  */
     36 
     37 #include "platform.h"
     38 #include <curl/curl.h>
     39 #include <microhttpd.h>
     40 #include <stdlib.h>
     41 #include <string.h>
     42 
     43 #include "mhd_zzuf_common.h"
     44 
     45 #define TEST_MAGIC_MARKER 0xFEE1C0DE
     46 
     47 #define DENIED_PAGE "Access denied."
     48 #define GRANTED_PAGE "Access granted."
     49 
     50 /**
     51  * The port offset used if the port cannot be auto-detected.
     52  */
     53 #define TEST_PORT_OFFSET 165
     54 
     55 #define TEST_REALM "TestRealm"
     56 #define TEST_USERNAME "testuser"
     57 #define TEST_PASSWORD "testpass"
     58 
     59 #define TEST_URI "/bauth"
     60 
     61 #define HOST_HDR "Host: " ZZUF_MHD_LISTEN_IP "\r\n"
     62 
     63 /**
     64  * The size of the buffer used to build the raw requests.
     65  */
     66 #define RAW_BUF_SIZE 8192
     67 
     68 
     69 /**
     70  * The closure of the access handler callback.
     71  */
     72 struct ahc_param
     73 {
     74   /**
     75    * Must have #TEST_MAGIC_MARKER value.
     76    */
     77   unsigned int magic;
     78 
     79   /**
     80    * Non-zero if any error has been encountered.
     81    */
     82   unsigned int err_flag;
     83 
     84   /**
     85    * The checksum over the decoded credentials, only used so that the
     86    * compiler cannot optimise the memory accesses away.
     87    */
     88   unsigned int cred_sum;
     89 };
     90 
     91 
     92 static enum MHD_Result
     93 ahc_basic (void *cls,
     94            struct MHD_Connection *connection,
     95            const char *url,
     96            const char *method,
     97            const char *version,
     98            const char *upload_data,
     99            size_t *upload_data_size,
    100            void **req_cls)
    101 {
    102   static int marker;
    103   struct ahc_param *param = (struct ahc_param *) cls;
    104   struct MHD_BasicAuthInfo *creds;
    105   struct MHD_Response *response;
    106   enum MHD_Result ret;
    107   int authenticated = 0;
    108 
    109   (void) url; (void) method; (void) version; (void) upload_data;
    110 
    111   if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic))
    112   {
    113     fprintf (stderr, "The 'cls' parameter is invalid "
    114              "at line %d.\n", (int) __LINE__);
    115     fflush (stderr);
    116     abort ();
    117   }
    118   if (NULL == *req_cls)
    119   {
    120     *req_cls = &marker;
    121     return MHD_YES;
    122   }
    123   if ((NULL != upload_data_size) && (0 != *upload_data_size))
    124   {
    125     *upload_data_size = 0; /* Discard the body */
    126     return MHD_YES;
    127   }
    128 
    129   /* This is the call that decodes the (fuzzed or hand-crafted) client's
    130      "Authorization: Basic" header. */
    131   creds = MHD_basic_auth_get_username_password3 (connection);
    132   if (NULL != creds)
    133   {
    134     size_t i;
    135 
    136     if (NULL == creds->username)
    137     {
    138       fprintf (stderr, "The 'username' member is NULL "
    139                "at line %d.\n", (int) __LINE__);
    140       param->err_flag = 1;
    141     }
    142     else
    143     {
    144       /* Touch every byte, including the zero-termination that MHD
    145          guarantees. */
    146       for (i = 0; i <= creds->username_len; ++i)
    147         param->cred_sum += (unsigned int) (unsigned char) creds->username[i];
    148     }
    149     if (NULL == creds->password)
    150     {
    151       if (0 != creds->password_len)
    152       {
    153         fprintf (stderr, "The 'password' is NULL while 'password_len' is "
    154                  "not zero at line %d.\n", (int) __LINE__);
    155         param->err_flag = 1;
    156       }
    157     }
    158     else
    159     {
    160       for (i = 0; i <= creds->password_len; ++i)
    161         param->cred_sum += (unsigned int) (unsigned char) creds->password[i];
    162     }
    163     if ((NULL != creds->username) &&
    164         (MHD_STATICSTR_LEN_ (TEST_USERNAME) == creds->username_len) &&
    165         (0 == memcmp (creds->username, TEST_USERNAME,
    166                       MHD_STATICSTR_LEN_ (TEST_USERNAME))) &&
    167         (NULL != creds->password) &&
    168         (MHD_STATICSTR_LEN_ (TEST_PASSWORD) == creds->password_len) &&
    169         (0 == memcmp (creds->password, TEST_PASSWORD,
    170                       MHD_STATICSTR_LEN_ (TEST_PASSWORD))))
    171       authenticated = 1;
    172     MHD_free (creds);
    173   }
    174 
    175   if (authenticated)
    176   {
    177     response =
    178       MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (
    179                                                 GRANTED_PAGE),
    180                                               GRANTED_PAGE);
    181     if (NULL == response)
    182       return MHD_NO; /* External error */
    183     if (zzuf_use_close || ! zzuf_oneone)
    184       (void) MHD_add_response_header (response,
    185                                       MHD_HTTP_HEADER_CONNECTION, "close");
    186     ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
    187     MHD_destroy_response (response);
    188     return ret;
    189   }
    190 
    191   response =
    192     MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (DENIED_PAGE),
    193                                             DENIED_PAGE);
    194   if (NULL == response)
    195     return MHD_NO; /* External error */
    196   if (zzuf_use_close || ! zzuf_oneone)
    197     (void) MHD_add_response_header (response,
    198                                     MHD_HTTP_HEADER_CONNECTION, "close");
    199   ret = MHD_queue_basic_auth_required_response3 (connection, TEST_REALM,
    200                                                  MHD_NO, response);
    201   MHD_destroy_response (response);
    202   return ret;
    203 }
    204 
    205 
    206 /**
    207  * Build one hostile "Authorization: Basic" request.
    208  *
    209  * @param variant the number of the variant to build
    210  * @param[out] buf the buffer for the request
    211  * @param buf_size the size of @a buf
    212  * @return the length of the request, or zero on failure
    213  */
    214 static size_t
    215 build_hostile_request (unsigned int variant,
    216                        char *buf,
    217                        size_t buf_size)
    218 {
    219   static const char b64_alphabet[] =
    220     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    221   char big[4096];
    222   const char *value;
    223   size_t i;
    224   int len;
    225 
    226   switch (variant % 10)
    227   {
    228   case 0:
    229     /* A truncated Base64 payload (not a multiple of four). */
    230     value = "dGVzdHVzZXI";
    231     break;
    232   case 1:
    233     /* Characters outside the Base64 alphabet. */
    234     value = "!!!!\?\?\?\?===="; /* '?' escaped to avoid a trigraph */
    235     break;
    236   case 2:
    237     /* Valid Base64 that decodes to a string without ':' separator. */
    238     value = "dGVzdHVzZXI=";
    239     break;
    240   case 3:
    241     /* Valid Base64 that decodes to "\0:\0" (binary zeros). */
    242     value = "ADoA";
    243     break;
    244   case 4:
    245     /* Valid Base64 that decodes to ":" only (empty username). */
    246     value = "Og==";
    247     break;
    248   case 5:
    249     /* Padding in the middle of the payload. */
    250     value = "dGVz=dXNlcjpwYXNz";
    251     break;
    252   case 6:
    253     /* An empty value. */
    254     value = "";
    255     break;
    256   case 7:
    257     /* A very long, but valid, Base64 payload. */
    258     for (i = 0; i + 1 < sizeof(big); ++i)
    259       big[i] = b64_alphabet[i % 64];
    260     big[sizeof(big) - 1] = 0;
    261     value = big;
    262     break;
    263   case 8:
    264     /* Whitespace inside the payload. */
    265     value = "dGVz dXNl cjpw YXNz";
    266     break;
    267   case 9:
    268   default:
    269     /* An unknown scheme, and a second "Authorization" header. */
    270     len = snprintf (buf, buf_size,
    271                     "GET " TEST_URI " HTTP/1.1\r\n" HOST_HDR
    272                     "Authorization: Bearer dGVzdHVzZXI6dGVzdHBhc3M=\r\n"
    273                     "Authorization: Basic\r\n"
    274                     "Connection: close\r\n\r\n");
    275     if ((0 > len) || (buf_size <= (size_t) len))
    276       return 0;
    277     return (size_t) len;
    278   }
    279   len = snprintf (buf, buf_size,
    280                   "GET " TEST_URI " HTTP/1.1\r\n" HOST_HDR
    281                   "Authorization: Basic %s\r\n"
    282                   "Connection: close\r\n\r\n",
    283                   value);
    284   if ((0 > len) || (buf_size <= (size_t) len))
    285     return 0;
    286   return (size_t) len;
    287 }
    288 
    289 
    290 static unsigned int
    291 client_run (struct MHD_Daemon *d_extern,
    292             uint16_t port,
    293             void *cls)
    294 {
    295   struct ahc_param *param = (struct ahc_param *) cls;
    296   struct zzuf_curl_driver drv;
    297   struct zzuf_curl_sink sink;
    298   char *req;
    299   unsigned int i;
    300   unsigned int loops;
    301 
    302   loops = zzuf_loop_count ();
    303   if (! zzuf_curl_driver_init (&drv, d_extern))
    304     return 99; /* Not an MHD error */
    305   req = malloc (RAW_BUF_SIZE);
    306   if (NULL == req)
    307   {
    308     zzuf_curl_driver_deinit (&drv);
    309     return 99; /* Not an MHD error */
    310   }
    311 
    312   for (i = 0; i < loops; ++i)
    313   {
    314     CURL *c;
    315     struct zzuf_raw_part part;
    316     size_t req_len;
    317 
    318     fprintf (stderr, ".");
    319     /* 1. A well-formed Basic Authentication handshake by libcurl, going
    320        through the fuzzing layer. */
    321     c = zzuf_curl_setup (port, TEST_URI, &sink);
    322     if (NULL == c)
    323     {
    324       free (req);
    325       zzuf_curl_driver_deinit (&drv);
    326       return 99; /* Not an MHD error */
    327     }
    328     if ((CURLE_OK != curl_easy_setopt (c, CURLOPT_HTTPAUTH,
    329                                        (long) CURLAUTH_BASIC)) ||
    330         (CURLE_OK != curl_easy_setopt (c, CURLOPT_USERPWD,
    331                                        TEST_USERNAME ":" TEST_PASSWORD)))
    332     {
    333       curl_easy_cleanup (c);
    334       free (req);
    335       zzuf_curl_driver_deinit (&drv);
    336       return 99; /* Not an MHD error */
    337     }
    338     zzuf_curl_driver_perform (&drv, c);
    339     curl_easy_cleanup (c);
    340 
    341     /* 2. The hand-crafted hostile headers. */
    342     req_len = build_hostile_request (i, req, RAW_BUF_SIZE);
    343     if (0 != req_len)
    344     {
    345       part.data = req;
    346       part.size = req_len;
    347       /* Once through the fuzzing layer ... */
    348       if (ZZUF_RAW_SETUP_FAILED ==
    349           zzuf_raw_exchange (d_extern, port, &part, 1,
    350                              (unsigned int) ZZUF_CLIENT_TIMEOUT))
    351       {
    352         free (req);
    353         zzuf_curl_driver_deinit (&drv);
    354         return 99; /* Not an MHD error */
    355       }
    356       /* ... and once verbatim. */
    357       if (ZZUF_RAW_SETUP_FAILED ==
    358           zzuf_raw_exchange2 (d_extern, port, 1, &part, 1,
    359                               (unsigned int) ZZUF_CLIENT_TIMEOUT,
    360                               NULL, 0, NULL))
    361       {
    362         free (req);
    363         zzuf_curl_driver_deinit (&drv);
    364         return 99; /* Not an MHD error */
    365       }
    366     }
    367     fflush (stderr);
    368   }
    369   free (req);
    370   zzuf_curl_driver_deinit (&drv);
    371 
    372   if (0 != param->err_flag)
    373   {
    374     fprintf (stderr, "One or more errors have been detected by the access "
    375              "handler callback function. At line %d.\n", (int) __LINE__);
    376     return 1;
    377   }
    378   return 0;
    379 }
    380 
    381 
    382 int
    383 main (int argc, char *const *argv)
    384 {
    385   struct ahc_param param;
    386   struct zzuf_run_params rp;
    387   unsigned int res;
    388   int use_magic_exit_codes;
    389 
    390   zzuf_parse_common_args (argc, argv);
    391   use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run;
    392 
    393   if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_BASIC_AUTH))
    394   {
    395     fprintf (stderr, "Basic Authentication is not supported by this "
    396              "MHD build.\n");
    397     return use_magic_exit_codes ? 77 : 0;
    398   }
    399   res = zzuf_check_runnable ();
    400   if (0 != res)
    401     return use_magic_exit_codes ? (int) res : 0;
    402 
    403   if (CURLE_OK != curl_global_init (CURL_GLOBAL_WIN32))
    404   {
    405     fprintf (stderr, "curl_global_init() failed at line %d.\n",
    406              (int) __LINE__);
    407     return use_magic_exit_codes ? 99 : 0;
    408   }
    409 
    410   param.magic = (unsigned int) TEST_MAGIC_MARKER;
    411   param.err_flag = 0;
    412   param.cred_sum = 0;
    413 
    414   memset (&rp, 0, sizeof(rp));
    415   rp.port = zzuf_pick_port (TEST_PORT_OFFSET);
    416   rp.ahc = &ahc_basic;
    417   rp.ahc_cls = &param;
    418   rp.rcc = NULL;
    419   rp.rcc_cls = NULL;
    420   /* The hostile headers are large, use a memory limit that can hold them. */
    421   rp.mem_limit_override = 8192;
    422   rp.sweep_profiles = 1;
    423   rp.profile_start = 0;
    424   rp.client = &client_run;
    425   rp.client_cls = &param;
    426   rp.extra_opts = NULL;
    427 
    428   res = zzuf_run_polling_modes (&rp);
    429   curl_global_cleanup ();
    430 
    431   if (99 == res)
    432     return use_magic_exit_codes ? 99 : 0;
    433   if (77 == res)
    434     return use_magic_exit_codes ? 77 : 0;
    435   return (0 == res) ? 0 : 1; /* 0 == pass */
    436 }