GNU libmicrohttpd 1.0.9
Loading...
Searching...
No Matches
fuzz_str.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
43#define FUZZ_HARNESS_NAME "fuzz_str"
44#include "fuzz_common.h"
45
46#include "mhd_options.h"
47#include "mhd_str.h"
48
53#define MODEL_MAX_DIGEST 32
54
69
88
89
95static void *
96xalloc (size_t n)
97{
98 void *p = malloc ((0 == n) ? 1 : n);
99
100 if (NULL == p)
101 abort ();
102 return p;
103}
104
105
106static char *
107dup_z (const uint8_t *d,
108 size_t n)
109{
110 char *p = (char *) xalloc (n + 1);
111
112 memcpy (p, d, n);
113 p[n] = '\0';
114 return p;
115}
116
117
118int
120 size_t size)
121{
122 enum str_target tgt;
123 const uint8_t *p;
124 size_t n;
125 unsigned int aux;
126
127 if (size < 3)
128 return 0;
129 tgt = (enum str_target) (data[0] % (unsigned int) TGT_COUNT);
130 aux = data[1];
131 p = data + 2;
132 n = size - 2;
133 /* Keep the inputs small: these are unit-level primitives and short
134 inputs explore the interesting corner cases far more efficiently. */
135 if (n > 512)
136 n = 512;
137
138 switch (tgt)
139 {
140 case TGT_HEX_TO_BIN:
141 {
142 /* Contract: the output buffer must be len/2 bytes long, or
143 len/2 + 1 if len is odd. */
144 size_t need = (n / 2) + (n % 2);
145 uint8_t *out = (uint8_t *) xalloc (need);
146 size_t r = MHD_hex_to_bin ((const char *) p, n, out);
147
148 if (r > need)
149 fuzz_report_finding ("MHD_hex_to_bin() reported more bytes written "
150 "than its documented output size");
151 free (out);
152 break;
153 }
154
155 case TGT_BIN_TO_HEX:
156 {
157 char *out = (char *) xalloc (2 * n);
158 size_t r = MHD_bin_to_hex (p, n, out);
159
160 if (r != 2 * n)
161 fuzz_report_finding ("MHD_bin_to_hex() did not write 2 * size chars");
162 free (out);
163 break;
164 }
165
166 case TGT_BIN_TO_HEX_Z:
167 {
168 char *out = (char *) xalloc (2 * n + 1);
169 size_t r = MHD_bin_to_hex_z (p, n, out);
170
171 if (r != 2 * n)
172 fuzz_report_finding ("MHD_bin_to_hex_z() did not write 2 * size chars");
173 if ('\0' != out[r])
174 fuzz_report_finding ("MHD_bin_to_hex_z() result is not "
175 "zero-terminated");
176 free (out);
177 break;
178 }
179
180 case TGT_PCT_STRICT:
181 {
182 /* First with the exact maximum size, then with a buffer that is
183 deliberately too small. */
184 char *out = (char *) xalloc (n);
185 size_t r = MHD_str_pct_decode_strict_n_ ((const char *) p, n, out, n);
186 size_t small;
187
188 if (r > n)
189 fuzz_report_finding ("MHD_str_pct_decode_strict_n_() overran the "
190 "documented output size");
191 free (out);
192 small = (0 == n) ? 0 : (n * (aux % 100u)) / 100u;
193 out = (char *) xalloc (small);
194 (void) MHD_str_pct_decode_strict_n_ ((const char *) p, n, out, small);
195 free (out);
196 break;
197 }
198
199 case TGT_PCT_LENIENT:
200 {
201 char *out = (char *) xalloc (n);
202 bool broken = false;
203 size_t r =
204 MHD_str_pct_decode_lenient_n_ ((const char *) p, n, out, n, &broken);
205 size_t small;
206
207 if (r > n)
208 fuzz_report_finding ("MHD_str_pct_decode_lenient_n_() overran the "
209 "documented output size");
210 free (out);
211 small = (0 == n) ? 0 : (n * (aux % 100u)) / 100u;
212 out = (char *) xalloc (small);
213 (void) MHD_str_pct_decode_lenient_n_ ((const char *) p, n, out, small,
214 NULL);
215 free (out);
216 break;
217 }
218
220 {
221 char *s = dup_z (p, n);
223
224 if (r > n)
225 fuzz_report_finding ("MHD_str_pct_decode_in_place_strict_() grew "
226 "the string");
227 free (s);
228 break;
229 }
230
232 {
233 char *s = dup_z (p, n);
234 bool broken = false;
235 size_t r = MHD_str_pct_decode_in_place_lenient_ (s, &broken);
236
237 if (r > n)
238 fuzz_report_finding ("MHD_str_pct_decode_in_place_lenient_() grew "
239 "the string");
240 free (s);
241 break;
242 }
243
244#ifdef DAUTH_SUPPORT
245 case TGT_UNQUOTE:
246 {
247 /* Unquoting never grows the string. */
248 char *out = (char *) xalloc (n);
249 size_t r = MHD_str_unquote ((const char *) p, n, out);
250
251 if (r > n)
252 fuzz_report_finding ("MHD_str_unquote() wrote more characters than "
253 "the quoted input had");
254 free (out);
255 break;
256 }
257#else /* ! DAUTH_SUPPORT */
258 case TGT_UNQUOTE:
259 break;
260#endif /* ! DAUTH_SUPPORT */
261
262#if defined(DAUTH_SUPPORT) || defined(BAUTH_SUPPORT)
263 case TGT_QUOTE:
264 {
265 /* Quoting can at most double the size. */
266 char *out = (char *) xalloc (2 * n);
267 size_t r = MHD_str_quote ((const char *) p, n, out, 2 * n);
268 size_t small;
269
270 if (r > 2 * n)
271 fuzz_report_finding ("MHD_str_quote() wrote more than 2 * len chars");
272 free (out);
273 small = (0 == n) ? 0 : (n * (aux % 200u)) / 100u;
274 out = (char *) xalloc (small);
275 (void) MHD_str_quote ((const char *) p, n, out, small);
276 free (out);
277 break;
278 }
279#else /* ! (DAUTH_SUPPORT || BAUTH_SUPPORT) */
280 case TGT_QUOTE:
281 break;
282#endif /* ! (DAUTH_SUPPORT || BAUTH_SUPPORT) */
283
284#ifdef BAUTH_SUPPORT
285 case TGT_BASE64:
286 {
287 size_t need = MHD_base64_max_dec_size_ (n);
288 uint8_t *out = (uint8_t *) xalloc (need);
289 size_t r = MHD_base64_to_bin_n ((const char *) p, n, out, need);
290 size_t small;
291
292 if (r > need)
293 fuzz_report_finding ("MHD_base64_to_bin_n() exceeded "
294 "MHD_base64_max_dec_size_()");
295 free (out);
296 small = (0 == need) ? 0 : (need * (aux % 100u)) / 100u;
297 out = (uint8_t *) xalloc (small);
298 (void) MHD_base64_to_bin_n ((const char *) p, n, out, small);
299 free (out);
300 break;
301 }
302#else /* ! BAUTH_SUPPORT */
303 case TGT_BASE64:
304 break;
305#endif /* ! BAUTH_SUPPORT */
306
307 case TGT_TO_UINT64:
308 {
309 char *s = dup_z (p, n);
310 uint64_t v1 = 0;
311 uint64_t v2 = 0;
312 size_t r1 = MHD_str_to_uint64_n_ (s, n, &v1);
313 size_t r2 = MHD_strx_to_uint64_n_ (s, n, &v2);
314
315 if (r1 > n)
316 fuzz_report_finding ("MHD_str_to_uint64_n_() consumed more than "
317 "maxlen characters");
318 if (r2 > n)
319 fuzz_report_finding ("MHD_strx_to_uint64_n_() consumed more than "
320 "maxlen characters");
321 free (s);
322 break;
323 }
324
325 case TGT_TOKENS:
326 {
327 /* The "token" arguments have documented preconditions (no NUL,
328 space, tab or comma); a fuzzer that violates them would only
329 find its own mistakes, so sanitise them here. */
330 char *s = dup_z (p, n);
331 size_t split = (0 == n) ? 0 : (aux % n);
332 char *tok = dup_z (p + split, n - split);
333 size_t tlen;
334 ssize_t bs;
335 char *out;
336 size_t k;
337
338 for (k = 0; k < n - split; k++)
339 {
340 if ( ('\0' == tok[k]) || (' ' == tok[k]) ||
341 ('\t' == tok[k]) || (',' == tok[k]) )
342 tok[k] = 'x';
343 }
344 tlen = strlen (tok);
345 if (0 == tlen)
346 {
347 free (tok);
348 tok = dup_z ((const uint8_t *) "chunked", 7);
349 tlen = 7;
350 }
351 (void) MHD_str_has_token_caseless_ (s, tok, tlen);
352
353 /* Documented worst case growth of the output is 50%. */
354 bs = (ssize_t) (n + n / 2 + 1);
355 out = (char *) xalloc ((size_t) bs);
356 (void) MHD_str_remove_token_caseless_ (s, n, tok, tlen, out, &bs);
357 if (bs > (ssize_t) (n + n / 2 + 1))
358 fuzz_report_finding ("MHD_str_remove_token_caseless_() reported a "
359 "result larger than the documented 50% growth");
360 if (0 <= bs)
361 {
362 /* The output of the previous call is normalised, which is the
363 documented precondition of the in-place variant. */
364 char *norm = dup_z ((const uint8_t *) out, (size_t) bs);
365 size_t nlen = (size_t) bs;
366
367 (void) MHD_str_remove_tokens_caseless_ (norm, &nlen, tok, tlen);
368 if (nlen > (size_t) bs)
369 fuzz_report_finding ("MHD_str_remove_tokens_caseless_() grew the "
370 "string");
371 free (norm);
372 }
373 free (out);
374 free (tok);
375 free (s);
376 break;
377 }
378
380 {
381 size_t split = (0 == n) ? 0 : (aux % n);
382 char *a = dup_z (p, split);
383 char *b = dup_z (p + split, n - split);
384
385 (void) MHD_str_equal_caseless_bin_n_ (a, b, (split < n - split)
386 ? split : (n - split));
387 (void) MHD_str_equal_caseless_n_ (a, b, n);
388 free (a);
389 free (b);
390 break;
391 }
392
394 {
395 /* See the comment on model_digest_sink above. */
396 size_t len = n;
397 uint8_t *out;
398
400 {
401 const char *e = getenv ("MHD_FUZZ_MODEL_DIGEST_SINK");
402
404 if (NULL != e)
405 model_digest_sink = (0 != atoi (e));
406 }
407 if (! model_digest_sink)
408 break;
409 /* digestauth.c only checked 'len <= 4 * digest_size' before the
410 fix; replay exactly that bound. */
411 if (len > 4 * MODEL_MAX_DIGEST)
412 len = 4 * MODEL_MAX_DIGEST;
413 out = (uint8_t *) xalloc (MODEL_MAX_DIGEST);
414 (void) MHD_hex_to_bin ((const char *) p, len, out);
415 free (out);
416 break;
417 }
418
419 case TGT_COUNT:
420 default:
421 break;
422 }
423 return 0;
424}
425
426
427/* ------------------------------------------------------------------ */
428/* Generator */
429/* ------------------------------------------------------------------ */
430
431static const char *const gen_str_atoms[] = {
432 "%41", "%", "%%", "%zz", "%0", "%00", "%ff", "\\", "\\\"", "\"",
433 "0123456789abcdef", "0123456789ABCDEF", "ffffffffffffffffffffffff",
434 "gg", "0x", "18446744073709551615", "99999999999999999999",
435 "a, b, c", "chunked", "identity", " , ", ",,", "token",
436 "QUJD", "QQ==", "Q===", "====", "AAAA", "AA=A",
437 "\x00\x01\x7f\x80\xff", " ", "\t", "\r\n"
438};
439
440
441static size_t
443 uint8_t *buf,
444 size_t cap)
445{
446 size_t len = 0;
447 unsigned int natoms;
448 unsigned int i;
449
450 if (cap < 8)
451 return 0;
452 buf[len++] = (uint8_t) fuzz_below (rng, (uint32_t) TGT_COUNT);
453 buf[len++] = fuzz_byte (rng);
454 natoms = 1 + fuzz_below (rng, 12);
455 for (i = 0; i < natoms; i++)
456 {
457 if (fuzz_chance (rng, 3))
458 {
459 /* a run of hex digits of a length that is interesting for the
460 digest code paths (32, 64, 128 characters) */
461 unsigned int k;
462 unsigned int run = 1 + fuzz_below (rng, 140);
463
464 for (k = 0; (k < run) && (len < cap); k++)
465 buf[len++] = (uint8_t) "0123456789abcdef"[fuzz_below (rng, 16)];
466 }
467 else if (fuzz_chance (rng, 8))
468 {
469 if (len < cap)
470 buf[len++] = fuzz_byte (rng);
471 }
472 else
473 {
474 const char *a =
476 (uint32_t) (sizeof (gen_str_atoms)
477 / sizeof (char *)))];
478 size_t al = strlen (a);
479
480 if (len + al > cap)
481 break;
482 memcpy (buf + len, a, al);
483 len += al;
484 }
485 }
486 return len;
487}
488
489
490/* ------------------------------------------------------------------ */
491/* Seed corpus */
492/* ------------------------------------------------------------------ */
493
494struct str_seed
495{
496 const char *txt;
497 size_t len;
498};
499
500#define SSEED(t) { t, sizeof (t) - 1 }
501
502static const struct str_seed str_seeds[] = {
503 /* hex -> bin, 128 characters: the length digestauth.c used to allow
504 into a 32 byte buffer */
505 SSEED ("\x00\x00"
506 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
507 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"),
508 SSEED ("\x00\x00" "0123456789abcdef0123456789abcdef"),
509 SSEED ("\x00\x00" "abc"),
510 SSEED ("\x00\x00" "zz"),
511 SSEED ("\x01\x00" "\x01\x02\x03\x04"),
512 SSEED ("\x02\x00" "\xff\xfe"),
513 SSEED ("\x03\x00" "/a%41%42%zz%"),
514 SSEED ("\x04\x40" "/a%41%42%zz%"),
515 SSEED ("\x05\x00" "%41%42%%%0"),
516 SSEED ("\x06\x00" "%41%42%%%0"),
517 SSEED ("\x07\x00" "a\\\"b\\\\c"),
518 SSEED ("\x08\x00" "a\"b\\c"),
519 SSEED ("\x09\x00" "QUJDRA=="),
520 SSEED ("\x09\x00" "QUJDR==="),
521 SSEED ("\x0a\x00" "18446744073709551615"),
522 SSEED ("\x0a\x00" "ffffffffffffffff"),
523 SSEED ("\x0b\x03" "chunked, identity, chunked"),
524 SSEED ("\x0c\x04" "CHUNKEDchunked"),
525 SSEED ("\x0d\x00"
526 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
527 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
528};
529
530
531static size_t
533{
534 return sizeof (str_seeds) / sizeof (str_seeds[0]);
535}
536
537
538static const uint8_t *
539fuzz_seed_get (size_t idx,
540 size_t *len)
541{
542 *len = str_seeds[idx].len;
543 return (const uint8_t *) str_seeds[idx].txt;
544}
Shared, header-only fuzzing driver for the MHD in-process fuzzers.
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 size_t fuzz_seed_count(void)
Definition fuzz_str.c:532
static const char *const gen_str_atoms[]
Definition fuzz_str.c:431
#define SSEED(t)
Definition fuzz_str.c:500
static int model_digest_sink_read
Definition fuzz_str.c:68
static const uint8_t * fuzz_seed_get(size_t idx, size_t *len)
Definition fuzz_str.c:539
static int model_digest_sink
Definition fuzz_str.c:67
#define MODEL_MAX_DIGEST
Definition fuzz_str.c:53
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
Definition fuzz_str.c:119
static void * xalloc(size_t n)
Definition fuzz_str.c:96
static size_t fuzz_generate(struct fuzz_rng *rng, uint8_t *buf, size_t cap)
Definition fuzz_str.c:442
static const struct str_seed str_seeds[]
Definition fuzz_str.c:502
str_target
Definition fuzz_str.c:71
@ TGT_QUOTE
Definition fuzz_str.c:80
@ TGT_BIN_TO_HEX
Definition fuzz_str.c:73
@ TGT_PCT_IN_PLACE_LENIENT
Definition fuzz_str.c:78
@ TGT_PCT_STRICT
Definition fuzz_str.c:75
@ TGT_TO_UINT64
Definition fuzz_str.c:82
@ TGT_DIGEST_HEX_SINK
Definition fuzz_str.c:85
@ TGT_BASE64
Definition fuzz_str.c:81
@ TGT_TOKENS
Definition fuzz_str.c:83
@ TGT_BIN_TO_HEX_Z
Definition fuzz_str.c:74
@ TGT_UNQUOTE
Definition fuzz_str.c:79
@ TGT_PCT_LENIENT
Definition fuzz_str.c:76
@ TGT_COUNT
Definition fuzz_str.c:86
@ TGT_PCT_IN_PLACE_STRICT
Definition fuzz_str.c:77
@ TGT_HEX_TO_BIN
Definition fuzz_str.c:72
@ TGT_EQUAL_CASELESS
Definition fuzz_str.c:84
static char * dup_z(const uint8_t *d, size_t n)
Definition fuzz_str.c:107
#define NULL
additional automatic macros for MHD_config.h
size_t MHD_bin_to_hex(const void *bin, size_t size, char *hex)
Definition mhd_str.c:1677
size_t MHD_str_pct_decode_strict_n_(const char *pct_encoded, size_t pct_encoded_len, char *decoded, size_t buf_size)
Definition mhd_str.c:1750
size_t MHD_bin_to_hex_z(const void *bin, size_t size, char *hex)
Definition mhd_str.c:1697
bool MHD_str_remove_tokens_caseless_(char *str, size_t *str_len, const char *const tokens, const size_t tokens_len)
Definition mhd_str.c:1034
size_t MHD_str_pct_decode_lenient_n_(const char *pct_encoded, size_t pct_encoded_len, char *decoded, size_t buf_size, bool *broken_encoding)
Definition mhd_str.c:1835
size_t MHD_str_to_uint64_n_(const char *str, size_t maxlen, uint64_t *out_val)
Definition mhd_str.c:1239
size_t MHD_str_pct_decode_in_place_lenient_(char *str, bool *broken_encoding)
Definition mhd_str.c:1994
size_t MHD_strx_to_uint64_n_(const char *str, size_t maxlen, uint64_t *out_val)
Definition mhd_str.c:1416
int MHD_str_equal_caseless_n_(const char *const str1, const char *const str2, size_t maxlen)
Definition mhd_str.c:718
size_t MHD_str_pct_decode_in_place_strict_(char *str)
Definition mhd_str.c:1938
bool MHD_str_has_token_caseless_(const char *str, const char *const token, size_t token_len)
Definition mhd_str.c:783
bool MHD_str_remove_token_caseless_(const char *str, size_t str_len, const char *const token, const size_t token_len, char *buf, ssize_t *buf_size)
Definition mhd_str.c:858
bool MHD_str_equal_caseless_bin_n_(const char *const str1, const char *const str2, size_t len)
Definition mhd_str.c:750
size_t MHD_hex_to_bin(const char *hex, size_t len, void *bin)
Definition mhd_str.c:1711
Header for string manipulating helpers.
void * data