GNU libmicrohttpd 1.0.9
Loading...
Searching...
No Matches
fuzz_postprocessor.c
Go to the documentation of this file.
1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2026 Christian Grothoff
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library.
17 If not, see <http://www.gnu.org/licenses/>.
18*/
19
44#define FUZZ_HARNESS_NAME "fuzz_postprocessor"
45#include "fuzz_common.h"
46
47/* internal.h pulls in MHD_config.h and <microhttpd.h> in the right
48 order; including <microhttpd.h> first would redefine _MHD_EXTERN. */
49#include "internal.h"
50
51static const size_t pp_buf_sizes[] = {
52 256, 257, 300, 512, 1024, 2048, 4096, 65536
53};
54
55static const size_t chunk_patterns[] = {
56 1, 2, 3, 5, 7, 13, 32, 64, 1024, 0 /* 0 == everything at once */
57};
58
60
62static unsigned long stat_pp_created;
63static unsigned long stat_pp_failed;
64static unsigned long stat_values;
65
66
67static enum MHD_Result
68dummy_ahc (void *cls,
69 struct MHD_Connection *connection,
70 const char *url,
71 const char *method,
72 const char *version,
73 const char *upload_data,
74 size_t *upload_data_size,
75 void **req_cls)
76{
77 (void) cls; (void) connection; (void) url; (void) method; (void) version;
78 (void) upload_data; (void) upload_data_size; (void) req_cls;
79 return MHD_NO;
80}
81
82
83static void
92
93
94static void
96{
97 if (! fuzz_verbose)
98 return;
99 fprintf (stderr,
100 "%s: post processors created=%lu rejected=%lu, "
101 "values reported=%lu\n",
104}
105
106
107static struct MHD_Daemon *
109{
110 if (NULL == shared_daemon)
111 {
115 0, NULL, NULL, &dummy_ahc, NULL,
117 if (NULL != shared_daemon)
118 {
119 (void) atexit (&stop_shared_daemon);
120 (void) atexit (&print_stats);
121 }
122 }
123 return shared_daemon;
124}
125
126
127struct pp_ctx
128{
129 const char *data;
130 size_t data_len;
131 uint64_t max_off;
132};
133
134
135static enum MHD_Result
136post_iter (void *cls,
137 enum MHD_ValueKind kind,
138 const char *key,
139 const char *filename,
140 const char *content_type,
141 const char *transfer_encoding,
142 const char *data,
143 uint64_t off,
144 size_t size)
145{
146 struct pp_ctx *ctx = (struct pp_ctx *) cls;
147 volatile size_t sink = 0;
148
149 (void) kind;
150 stat_values++;
151 /* Touch every string the post processor claims to have produced;
152 ASAN turns any dangling or out-of-bounds pointer into an error. */
153 if (NULL != key)
154 sink += strlen (key);
155 if (NULL != filename)
156 sink += strlen (filename);
157 if (NULL != content_type)
158 sink += strlen (content_type);
159 if (NULL != transfer_encoding)
160 sink += strlen (transfer_encoding);
161 if ( (NULL != data) && (0 != size) )
162 {
163 size_t i;
164
165 for (i = 0; i < size; i++)
166 sink += (size_t) (unsigned char) data[i];
167 }
168 else if (NULL == data)
169 {
170 if (0 != size)
171 fuzz_report_finding ("MHD_PostDataIterator called with NULL data but "
172 "non-zero size");
173 }
174 /* A single value is delivered in order and can never be longer than
175 the POST data that was fed in. */
176 if (off + size > ctx->data_len + 1)
177 fuzz_report_finding ("MHD_PostDataIterator reported more value data "
178 "than the POST data contained");
179 if (off > ctx->max_off + ctx->data_len)
180 fuzz_report_finding ("MHD_PostDataIterator offset jumped implausibly");
181 ctx->max_off = off;
182 (void) sink;
183 return MHD_YES;
184}
185
186
187int
189 size_t size)
190{
191 struct MHD_Connection c;
192 struct MHD_HTTP_Req_Header h;
193 struct MHD_PostProcessor *pp;
194 struct pp_ctx ctx;
195 char *ctype;
196 char *body;
197 size_t body_len;
198 size_t buf_size;
199 size_t chunk;
200 size_t off;
201 size_t blen;
202 unsigned int sel;
203
204 if (size < 5)
205 return 0;
206 if (NULL == get_shared_daemon ())
207 return 0;
208
209 sel = data[0];
210 buf_size = pp_buf_sizes[data[1] % (sizeof (pp_buf_sizes)
211 / sizeof (pp_buf_sizes[0]))];
212 chunk = chunk_patterns[data[2] % (sizeof (chunk_patterns)
213 / sizeof (chunk_patterns[0]))];
214 blen = data[3];
215
216 body_len = size - 4;
217 if (body_len > 8192)
218 body_len = 8192;
219 if (blen > body_len)
220 blen = body_len;
221
222 /* Exactly sized copies so that ASAN catches reads past the end. */
223 body = (char *) malloc (body_len + 1);
224 if (NULL == body)
225 return 0;
226 memcpy (body, data + 4, body_len);
227 body[body_len] = '\0';
228
229 ctype = (char *) malloc (blen + 64);
230 if (NULL == ctype)
231 {
232 free (body);
233 return 0;
234 }
235 switch (sel % 6)
236 {
237 case 0:
240 break;
241 case 1:
242 case 2:
243 {
244 /* multipart with a boundary taken from the payload */
245 size_t o = 0;
246 size_t i;
247
251 memcpy (ctype + o, "; boundary=", 11);
252 o += 11;
253 for (i = 0; i < blen; i++)
254 {
255 char ch = body[i];
256
257 /* A NUL would truncate the header value; map it away. */
258 ctype[o++] = ('\0' == ch) ? 'x' : ch;
259 }
260 ctype[o] = '\0';
261 break;
262 }
263 case 3:
264 (void) snprintf (ctype, blen + 64,
265 "multipart/form-data; boundary=\"%.*s\"",
266 (int) ((blen > 40) ? 40 : blen), body);
267 break;
268 case 4:
271 break;
272 default:
273 memcpy (ctype, "text/plain", sizeof ("text/plain"));
274 break;
275 }
276
277 memset (&c, 0, sizeof (c));
278 memset (&h, 0, sizeof (h));
281 h.value = ctype;
282 h.value_size = strlen (ctype);
285 c.rq.headers_received = &h;
288
289 ctx.data = body;
290 ctx.data_len = body_len;
291 ctx.max_off = 0;
292
293 pp = MHD_create_post_processor (&c, buf_size, &post_iter, &ctx);
294 if (NULL == pp)
295 {
297 free (ctype);
298 free (body);
299 return 0;
300 }
302
303 off = 0;
304 while (off < body_len)
305 {
306 size_t n = (0 == chunk) ? (body_len - off) : chunk;
307
308 if (n > body_len - off)
309 n = body_len - off;
310 (void) MHD_post_process (pp, body + off, n);
311 off += n;
312 }
313 /* Final, zero-length call: what MHD itself does at the end of the
314 upload. */
315 (void) MHD_post_process (pp, body + body_len, 0);
316 (void) MHD_destroy_post_processor (pp);
317
318 free (ctype);
319 free (body);
320 return 0;
321}
322
323
324/* ------------------------------------------------------------------ */
325/* Generator */
326/* ------------------------------------------------------------------ */
327
328static const char *const gen_disp[] = {
329 "Content-Disposition: form-data; name=\"a\"",
330 "Content-Disposition: form-data; name=\"a\"; filename=\"f.txt\"",
331 "Content-Disposition: form-data; name=a",
332 "Content-Disposition: form-data",
333 "Content-Disposition: attachment; name=\"a\"",
334 "Content-Disposition: form-data; name=\"\"",
335 "Content-Disposition: form-data; name=\"a",
336 "Content-Type: text/plain",
337 "Content-Transfer-Encoding: binary",
338 "X-Other: value"
339};
340
341static const char *const gen_kv[] = {
342 "a=1", "b=%41", "c", "d=", "=e", "&", "&&", "a=%", "a=%4", "a=%zz",
343 "verylongkeyname=verylongvaluewithlotsofcharacters", "a+b=c+d",
344 "%41%42=%43%44"
345};
346
347
348static size_t
350 uint8_t *buf,
351 size_t cap)
352{
353 size_t len = 0;
354 int multipart;
355 const char *boundary;
356 unsigned int nparts;
357 unsigned int i;
358 static const char *const boundaries[] = {
359 "--abc", "XY", "boundary", "-", "aa", "0123456789012345678901234567890",
360 "a\"b"
361 };
362
363#define ADD(s) \
364 do { \
365 const char *s_ = (s); \
366 size_t l_ = strlen (s_); \
367 if (len + l_ >= cap) \
368 return len; \
369 memcpy (buf + len, s_, l_); \
370 len += l_; \
371 } while (0)
372
373 if (cap < 64)
374 return 0;
375 multipart = ! fuzz_chance (rng, 3);
376 boundary = boundaries[fuzz_below (rng,
377 (uint32_t) (sizeof (boundaries)
378 / sizeof (char *)))];
379 buf[len++] = (uint8_t) (multipart ? (1 + fuzz_below (rng, 2)) : 0);
380 buf[len++] = fuzz_byte (rng);
381 buf[len++] = fuzz_byte (rng);
382 buf[len++] = (uint8_t) strlen (boundary);
383 if (multipart)
384 {
385 /* The first strlen(boundary) bytes of the body double as the
386 boundary in the Content-Type header (see the input format). */
387 ADD (boundary);
388 nparts = 1 + fuzz_below (rng, 4);
389 for (i = 0; i < nparts; i++)
390 {
391 unsigned int nhdr = fuzz_below (rng, 3);
392 unsigned int k;
393
394 ADD ("\r\n--");
395 ADD (boundary);
396 ADD ("\r\n");
397 for (k = 0; k <= nhdr; k++)
398 {
399 ADD (gen_disp[fuzz_below (rng,
400 (uint32_t) (sizeof (gen_disp)
401 / sizeof (char *)))]);
402 ADD ("\r\n");
403 }
404 ADD ("\r\n");
405 {
406 unsigned int n = fuzz_below (rng, 40);
407
408 for (k = 0; (k < n) && (len < cap); k++)
409 buf[len++] = (uint8_t) ('A' + fuzz_below (rng, 26));
410 }
411 }
412 ADD ("\r\n--");
413 ADD (boundary);
414 ADD (fuzz_chance (rng, 4) ? "\r\n" : "--\r\n");
415 }
416 else
417 {
418 unsigned int n = 1 + fuzz_below (rng, 10);
419
420 for (i = 0; i < n; i++)
421 {
422 if (0 != i)
423 ADD ("&");
424 ADD (gen_kv[fuzz_below (rng,
425 (uint32_t) (sizeof (gen_kv) / sizeof (char *)))]);
426 }
427 }
428#undef ADD
429 return len;
430}
431
432
433/* ------------------------------------------------------------------ */
434/* Seed corpus */
435/* ------------------------------------------------------------------ */
436
437struct pp_seed
438{
439 const char *txt;
440 size_t len;
441};
442
443#define PSEED(t) { t, sizeof (t) - 1 }
444
445static const struct pp_seed pp_seeds[] = {
446 PSEED ("\x00\x00\x00\x00" "a=1&b=%41&c"),
447 PSEED ("\x00\x00\x04\x00" "a=1&b=%41&c"),
448 PSEED ("\x00\x00\x00\x00" "a=%"),
449 PSEED ("\x00\x00\x00\x00" "&&&&"),
450 PSEED ("\x01\x00\x00\x05" "--abc\r\n----abc\r\n"
451 "Content-Disposition: form-data; name=\"k\"\r\n\r\nvalue\r\n"
452 "----abc--\r\n"),
453 PSEED ("\x01\x00\x01\x05" "--abc\r\n----abc\r\n"
454 "Content-Disposition: form-data; name=\"k\"; filename=\"f\"\r\n"
455 "Content-Type: text/plain\r\n\r\nvalue\r\n----abc--\r\n"),
456 PSEED ("\x01\x07\x02\x02" "XY\r\n--XY\r\n\r\nnoheaders\r\n--XY--\r\n"),
457 PSEED ("\x01\x00\x00\x01" "-\r\n---\r\n\r\nx\r\n-----\r\n"),
458 PSEED ("\x04\x00\x00\x00" "no boundary at all"),
459 PSEED ("\x05\x00\x00\x00" "text/plain body"),
460 PSEED ("\x03\x00\x00\x03" "a\"b\r\n--a\"b\r\n\r\nv\r\n--a\"b--\r\n")
461};
462
463
464static size_t
466{
467 return sizeof (pp_seeds) / sizeof (pp_seeds[0]);
468}
469
470
471static const uint8_t *
472fuzz_seed_get (size_t idx,
473 size_t *len)
474{
475 *len = pp_seeds[idx].len;
476 return (const uint8_t *) pp_seeds[idx].txt;
477}
Shared, header-only fuzzing driver for the MHD in-process fuzzers.
static FUZZ_UNUSED int fuzz_verbose
static FUZZ_UNUSED uint32_t fuzz_below(struct fuzz_rng *r, uint32_t n)
static FUZZ_UNUSED uint8_t fuzz_byte(struct fuzz_rng *r)
static FUZZ_UNUSED void fuzz_report_finding(const char *what)
static FUZZ_UNUSED int fuzz_chance(struct fuzz_rng *r, uint32_t n)
static struct MHD_Daemon * get_shared_daemon(void)
static const size_t chunk_patterns[]
static unsigned long stat_pp_failed
static size_t fuzz_seed_count(void)
static unsigned long stat_pp_created
static const size_t pp_buf_sizes[]
static void stop_shared_daemon(void)
static const char *const gen_kv[]
#define ADD(s)
#define FUZZ_HARNESS_NAME
#define PSEED(t)
static const uint8_t * fuzz_seed_get(size_t idx, size_t *len)
static enum MHD_Result post_iter(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
static const char *const gen_disp[]
static size_t fuzz_generate(struct fuzz_rng *rng, uint8_t *buf, size_t cap)
static void print_stats(void)
static unsigned long stat_values
static struct MHD_Daemon * shared_daemon
static enum MHD_Result dummy_ahc(void *cls, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **req_cls)
static const struct pp_seed pp_seeds[]
_MHD_EXTERN void MHD_stop_daemon(struct MHD_Daemon *daemon)
Definition daemon.c:9415
_MHD_EXTERN struct MHD_Daemon * MHD_start_daemon(unsigned int flags, uint16_t port, MHD_AcceptPolicyCallback apc, void *apc_cls, MHD_AccessHandlerCallback dh, void *dh_cls,...)
Definition daemon.c:6217
#define MHD_HTTP_HEADER_CONTENT_TYPE
Definition microhttpd.h:602
#define MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA
#define MHD_HTTP_POST_ENCODING_FORM_URLENCODED
_MHD_EXTERN enum MHD_Result MHD_destroy_post_processor(struct MHD_PostProcessor *pp)
_MHD_EXTERN enum MHD_Result MHD_post_process(struct MHD_PostProcessor *pp, const char *post_data, size_t post_data_len)
_MHD_EXTERN struct MHD_PostProcessor * MHD_create_post_processor(struct MHD_Connection *connection, size_t buffer_size, MHD_PostDataIterator iter, void *iter_cls)
MHD internal shared structures.
@ MHD_CONNECTION_HEADERS_PROCESSED
Definition internal.h:646
#define NULL
#define MHD_STATICSTR_LEN_(macro)
@ MHD_OPTION_END
MHD_Result
Definition microhttpd.h:163
@ MHD_YES
Definition microhttpd.h:172
@ MHD_NO
Definition microhttpd.h:167
void * data
MHD_ValueKind
@ MHD_HEADER_KIND
@ MHD_USE_ERROR_LOG
@ MHD_USE_NO_LISTEN_SOCKET
struct MHD_Request rq
Definition internal.h:1371
enum MHD_CONNECTION_STATE state
Definition internal.h:1571
struct MHD_Daemon * daemon
Definition internal.h:1366
enum MHD_ValueKind kind
Definition internal.h:396
const char * value
Definition internal.h:386
const char * header
Definition internal.h:376
struct MHD_HTTP_Req_Header * headers_received
Definition internal.h:1117
struct MHD_HTTP_Req_Header * headers_received_tail
Definition internal.h:1122