test_pipelined.c (8621B)
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_pipelined.c 23 * @brief Fuzzing testcase for pipelined requests 24 * @author Christian Grothoff 25 * 26 * Several complete requests are written with a single send() call, so that 27 * MHD has to find the request boundaries inside one read buffer. Corrupting 28 * such a stream (or combining it with a connection memory pool that is too 29 * small to hold all pipelined requests at once) is the most direct way to 30 * produce a request boundary de-synchronisation. 31 * 32 * libcurl does not pipeline any more (HTTP/1.1 pipelining support was 33 * removed in libcurl 7.62), therefore the raw socket client is used. The 34 * traffic still goes to the same port and through the same zzuf/socat 35 * corruption layer as the libcurl based tests of this directory. 36 */ 37 38 #include "platform.h" 39 #include <curl/curl.h> 40 #include <microhttpd.h> 41 #include <stdlib.h> 42 #include <string.h> 43 44 #include "mhd_zzuf_common.h" 45 46 #define TEST_MAGIC_MARKER 0xFEE1C0DE 47 48 #define EMPTY_PAGE "Empty page." 49 50 /** 51 * The port offset used if the port cannot be auto-detected. 52 */ 53 #define TEST_PORT_OFFSET 150 54 55 /** 56 * The maximum size of a single pipelined batch. 57 */ 58 #define MAX_BATCH_SIZE 4096 59 60 #define HOST_HDR "Host: " ZZUF_MHD_LISTEN_IP "\r\n" 61 62 63 /** 64 * The closure of the access handler callback. 65 */ 66 struct ahc_param 67 { 68 /** 69 * Must have #TEST_MAGIC_MARKER value. 70 */ 71 unsigned int magic; 72 73 /** 74 * Non-zero if any error has been encountered. 75 */ 76 unsigned int err_flag; 77 78 /** 79 * The number of requests seen so far. 80 */ 81 unsigned int num_requests; 82 }; 83 84 85 /** 86 * The request templates that are concatenated into one pipelined batch. 87 * The last request of every batch is added by build_batch(). 88 */ 89 static const char *const req_templates[] = { 90 "GET /pipe/a HTTP/1.1\r\n" HOST_HDR "\r\n", 91 "GET /pipe/b?x=1&y HTTP/1.1\r\n" HOST_HDR "X-Pad: 0123456789\r\n\r\n", 92 "HEAD /pipe/c HTTP/1.1\r\n" HOST_HDR "\r\n", 93 "POST /pipe/d HTTP/1.1\r\n" HOST_HDR 94 "Content-Length: 5\r\n\r\nHELLO", 95 "PUT /pipe/e HTTP/1.1\r\n" HOST_HDR 96 "Transfer-Encoding: chunked\r\n\r\n5\r\nHELLO\r\n0\r\n\r\n", 97 /* An empty line before the request line: allowed to be skipped by MHD */ 98 "\r\nGET /pipe/f HTTP/1.1\r\n" HOST_HDR "\r\n", 99 /* A request with a body that is *not* announced: the following bytes 100 must not be swallowed as a body */ 101 "GET /pipe/g HTTP/1.1\r\n" HOST_HDR "\r\n" 102 }; 103 104 #define NUM_REQ_TEMPLATES \ 105 (sizeof(req_templates) / sizeof(req_templates[0])) 106 107 108 static enum MHD_Result 109 ahc_pipelined (void *cls, 110 struct MHD_Connection *connection, 111 const char *url, 112 const char *method, 113 const char *version, 114 const char *upload_data, 115 size_t *upload_data_size, 116 void **req_cls) 117 { 118 static int marker; 119 struct ahc_param *param = (struct ahc_param *) cls; 120 struct MHD_Response *response; 121 enum MHD_Result ret; 122 123 (void) url; (void) method; (void) version; (void) upload_data; 124 125 if ((NULL == param) || (TEST_MAGIC_MARKER != param->magic)) 126 { 127 fprintf (stderr, "The 'cls' parameter is invalid " 128 "at line %d.\n", (int) __LINE__); 129 fflush (stderr); 130 abort (); 131 } 132 if ((NULL == url) || (NULL == method) || (NULL == version)) 133 { 134 fprintf (stderr, "One of the mandatory string parameters is NULL " 135 "at line %d.\n", (int) __LINE__); 136 param->err_flag = 1; 137 return MHD_NO; 138 } 139 if (NULL == *req_cls) 140 { 141 *req_cls = ▮ 142 return MHD_YES; 143 } 144 if ((NULL != upload_data_size) && (0 != *upload_data_size)) 145 { 146 *upload_data_size = 0; /* Discard the body */ 147 return MHD_YES; 148 } 149 param->num_requests++; 150 151 response = 152 MHD_create_response_from_buffer_static (MHD_STATICSTR_LEN_ (EMPTY_PAGE), 153 EMPTY_PAGE); 154 if (NULL == response) 155 { 156 fprintf (stderr, "MHD_create_response_from_buffer_static() failed " 157 "at line %d.\n", (int) __LINE__); 158 return MHD_NO; /* External error, do not raise the error flag */ 159 } 160 ret = MHD_YES; 161 if (zzuf_use_close) 162 ret = MHD_add_response_header (response, 163 MHD_HTTP_HEADER_CONNECTION, 164 "close"); 165 if (MHD_YES == ret) 166 ret = MHD_queue_response (connection, MHD_HTTP_OK, response); 167 MHD_destroy_response (response); 168 return ret; 169 } 170 171 172 /** 173 * Build one pipelined batch of requests. 174 * 175 * @param iteration the number of the current iteration 176 * @param[out] buf the buffer to fill 177 * @param buf_size the size of @a buf 178 * @return the number of bytes written to @a buf 179 */ 180 static size_t 181 build_batch (unsigned int iteration, char *buf, size_t buf_size) 182 { 183 static const char last_req[] = 184 "GET /pipe/last HTTP/1.1\r\n" HOST_HDR "Connection: close\r\n\r\n"; 185 size_t pos = 0; 186 unsigned int i; 187 unsigned int num; 188 189 num = 2 + (iteration % 4); /* 2 to 5 requests per batch */ 190 for (i = 0; i < num; ++i) 191 { 192 const char *req = 193 req_templates[(iteration + i) % NUM_REQ_TEMPLATES]; 194 const size_t len = strlen (req); 195 196 if (pos + len + sizeof(last_req) > buf_size) 197 break; 198 memcpy (buf + pos, req, len); 199 pos += len; 200 } 201 if (pos + MHD_STATICSTR_LEN_ (last_req) <= buf_size) 202 { 203 memcpy (buf + pos, last_req, MHD_STATICSTR_LEN_ (last_req)); 204 pos += MHD_STATICSTR_LEN_ (last_req); 205 } 206 return pos; 207 } 208 209 210 static unsigned int 211 client_run (struct MHD_Daemon *d_extern, 212 uint16_t port, 213 void *cls) 214 { 215 struct ahc_param *param = (struct ahc_param *) cls; 216 unsigned int i; 217 unsigned int loops; 218 char batch[MAX_BATCH_SIZE]; 219 220 loops = zzuf_loop_count (); 221 for (i = 0; i < loops; ++i) 222 { 223 struct zzuf_raw_part parts[2]; 224 size_t batch_len; 225 size_t num_parts; 226 227 fprintf (stderr, "."); 228 batch_len = build_batch (i, batch, sizeof(batch)); 229 parts[0].data = batch; 230 parts[0].size = batch_len; 231 num_parts = 1; 232 if (0 != i % 2) 233 { 234 /* Every other iteration: split the batch in the middle of a request, 235 so that MHD has to keep a partial request in the read buffer while 236 the previous ones are still being answered. */ 237 parts[0].size = batch_len / 2; 238 parts[1].data = batch + parts[0].size; 239 parts[1].size = batch_len - parts[0].size; 240 num_parts = 2; 241 } 242 if (ZZUF_RAW_SETUP_FAILED == 243 zzuf_raw_exchange (d_extern, port, parts, num_parts, 244 (unsigned int) ZZUF_CLIENT_TIMEOUT)) 245 { 246 fprintf (stderr, "The raw client could not be set up " 247 "at line %d.\n", (int) __LINE__); 248 return 99; /* Not an MHD error */ 249 } 250 fflush (stderr); 251 } 252 253 if (0 != param->err_flag) 254 { 255 fprintf (stderr, "One or more errors have been detected by the access " 256 "handler callback function. At line %d.\n", (int) __LINE__); 257 return 1; 258 } 259 return 0; 260 } 261 262 263 int 264 main (int argc, char *const *argv) 265 { 266 struct ahc_param param; 267 struct zzuf_run_params rp; 268 unsigned int res; 269 int use_magic_exit_codes; 270 271 zzuf_parse_common_args (argc, argv); 272 use_magic_exit_codes = zzuf_run_with_socat || zzuf_dry_run; 273 274 res = zzuf_check_runnable (); 275 if (0 != res) 276 return use_magic_exit_codes ? (int) res : 0; 277 278 param.magic = (unsigned int) TEST_MAGIC_MARKER; 279 param.err_flag = 0; 280 param.num_requests = 0; 281 282 memset (&rp, 0, sizeof(rp)); 283 rp.port = zzuf_pick_port (TEST_PORT_OFFSET); 284 rp.ahc = &ahc_pipelined; 285 rp.ahc_cls = ¶m; 286 rp.rcc = NULL; 287 rp.rcc_cls = NULL; 288 rp.mem_limit_override = 0; 289 rp.sweep_profiles = 1; 290 rp.profile_start = 0; 291 rp.client = &client_run; 292 rp.client_cls = ¶m; 293 294 res = zzuf_run_polling_modes (&rp); 295 296 if (99 == res) 297 return use_magic_exit_codes ? 99 : 0; 298 if (77 == res) 299 return use_magic_exit_codes ? 77 : 0; 300 return (0 == res) ? 0 : 1; /* 0 == pass */ 301 }