GNU libmicrohttpd 1.0.9
Loading...
Searching...
No Matches
fuzz_auth_header.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
45#define FUZZ_HARNESS_NAME "fuzz_auth_header"
46#include "fuzz_common.h"
47
48/* internal.h pulls in MHD_config.h and <microhttpd.h> in the right
49 order; including <microhttpd.h> first would redefine _MHD_EXTERN. */
50#include "internal.h"
51#include "memorypool.h"
52#include "gen_auth.h"
53#include "mhd_str.h"
54
55#ifdef DAUTH_SUPPORT
56#include "digestauth.h"
57#endif
58#ifdef BAUTH_SUPPORT
59#include "basicauth.h"
60#endif
61
62static const size_t pool_sizes[] = { 256, 512, 1024, 4096, 32768 };
63
64
72static char *
73fuzz_dup_n (const char *src,
74 size_t len)
75{
76 char *r = (char *) malloc (len + 1);
77
78 if (NULL == r)
79 return NULL;
80 memcpy (r, src, len);
81 r[len] = '\0';
82 return r;
83}
84
85
92
94static unsigned long stat_dauth_parsed;
95static unsigned long stat_dauth_failed;
96static unsigned long stat_bauth_parsed;
97static unsigned long stat_bauth_failed;
98
99
100static void
102{
103 if (! fuzz_verbose)
104 return;
105 fprintf (stderr,
106 "%s: digest headers parsed=%lu rejected=%lu; "
107 "basic headers parsed=%lu rejected=%lu\n",
111}
112
113
114static enum MHD_Result
115dummy_ahc (void *cls,
116 struct MHD_Connection *connection,
117 const char *url,
118 const char *method,
119 const char *version,
120 const char *upload_data,
121 size_t *upload_data_size,
122 void **req_cls)
123{
124 (void) cls; (void) connection; (void) url; (void) method; (void) version;
125 (void) upload_data; (void) upload_data_size; (void) req_cls;
126 return MHD_NO;
127}
128
129
130static void
132{
133 if (NULL != shared_daemon)
134 {
137 }
138}
139
140
141static struct MHD_Daemon *
143{
144 if (NULL == shared_daemon)
145 {
149 0, NULL, NULL, &dummy_ahc, NULL,
151 if (NULL != shared_daemon)
152 {
153 (void) atexit (&stop_shared_daemon);
154 (void) atexit (&print_stats);
155 }
156 }
157 return shared_daemon;
158}
159
160
161#ifdef DAUTH_SUPPORT
165static void
166check_param (const struct MHD_RqDAuthParam *pm,
167 const char *base,
168 size_t base_len,
169 const char *what)
170{
171 char msg[256];
172
173 if (NULL == pm->value.str)
174 {
175 if (0 != pm->value.len)
176 {
177 (void) snprintf (msg, sizeof (msg),
178 "digest parameter '%s' has NULL string but "
179 "non-zero length", what);
181 }
182 return;
183 }
184 if ( (pm->value.str < base) ||
185 (pm->value.str > base + base_len) ||
186 (pm->value.len > base_len) ||
187 (pm->value.str + pm->value.len > base + base_len) )
188 {
189 (void) snprintf (msg, sizeof (msg),
190 "digest parameter '%s' points outside of the "
191 "Authorization header value", what);
193 }
194}
195
196
197#endif /* DAUTH_SUPPORT */
198
199
200int
202 size_t size)
203{
204 static const char hdr_name[] = MHD_HTTP_HEADER_AUTHORIZATION;
205 struct MHD_Connection c;
206 struct MHD_HTTP_Req_Header h;
207 struct MemoryPool *pool;
208 char *value;
209 size_t vlen;
210 unsigned int sel;
211 size_t pool_size;
212
213 if (size < 2)
214 return 0;
215 if (NULL == get_shared_daemon ())
216 return 0;
217 sel = data[0];
218 vlen = size - 1;
219 if (vlen > 4096)
220 vlen = 4096;
221 pool_size = pool_sizes[(sel >> 3) % (sizeof (pool_sizes)
222 / sizeof (pool_sizes[0]))];
223
224 /* Exactly sized, zero-terminated copy of the header value, so that
225 ASAN catches any read past its end. */
226 value = (char *) malloc (vlen + 1);
227 if (NULL == value)
228 return 0;
229 memcpy (value, data + 1, vlen);
230 value[vlen] = '\0';
231
232 pool = MHD_pool_create (pool_size);
233 if (NULL == pool)
234 {
235 free (value);
236 return 0;
237 }
238
239 memset (&c, 0, sizeof (c));
240 memset (&h, 0, sizeof (h));
241 h.header = hdr_name;
243 h.value = value;
244 h.value_size = vlen;
247 c.pool = pool;
248 c.rq.headers_received = &h;
251
252#ifdef DAUTH_SUPPORT
253 if (0 == (sel & 0x01))
254 {
255 const struct MHD_RqDAuth *da;
256
257 da = MHD_get_rq_dauth_params_ (&c);
258 if (NULL == da)
260 else
261 {
263 check_param (&da->nonce, value, vlen, "nonce");
264 check_param (&da->opaque, value, vlen, "opaque");
265 check_param (&da->response, value, vlen, "response");
266 check_param (&da->username, value, vlen, "username");
267 check_param (&da->username_ext, value, vlen, "username*");
268 check_param (&da->realm, value, vlen, "realm");
269 check_param (&da->uri, value, vlen, "uri");
270 check_param (&da->qop_raw, value, vlen, "qop");
271 check_param (&da->cnonce, value, vlen, "cnonce");
272 check_param (&da->nc, value, vlen, "nc");
273 /* The result must be stable: a second call returns the cache. */
274 if (da != MHD_get_rq_dauth_params_ (&c))
275 fuzz_report_finding ("MHD_get_rq_dauth_params_() is not idempotent");
276 }
277 }
278#endif /* DAUTH_SUPPORT */
279#ifdef BAUTH_SUPPORT
280 if (0 != (sel & 0x01))
281 {
282 const struct MHD_RqBAuth *ba;
283
284 ba = MHD_get_rq_bauth_params_ (&c);
285 if (NULL == ba)
287 else
288 {
290 if (NULL != ba->token68.str)
291 {
292 if ( (ba->token68.str < value) ||
293 (ba->token68.str + ba->token68.len > value + vlen) )
294 fuzz_report_finding ("basic auth token68 points outside of the "
295 "Authorization header value");
296 /* Decoding must fit into the documented maximum size. */
297 if (0 != ba->token68.len)
298 {
299 size_t need = MHD_base64_max_dec_size_ (ba->token68.len);
300 uint8_t *bin = (uint8_t *) malloc ((0 == need) ? 1 : need);
301
302 if (NULL != bin)
303 {
304 size_t r = MHD_base64_to_bin_n (ba->token68.str,
305 ba->token68.len,
306 bin,
307 need);
308 if (r > need)
309 fuzz_report_finding ("MHD_base64_to_bin_n() exceeded "
310 "MHD_base64_max_dec_size_()");
311 free (bin);
312 }
313 }
314 }
315 if (ba != MHD_get_rq_bauth_params_ (&c))
316 fuzz_report_finding ("MHD_get_rq_bauth_params_() is not idempotent");
317 }
318 }
319#endif /* BAUTH_SUPPORT */
320
321#ifdef DAUTH_SUPPORT
322 /* The connection-less digest helpers. They belong here rather than
323 in fuzz_request because they are pure functions of their string
324 arguments: this harness reaches roughly two orders of magnitude
325 more executions per second, and -- more importantly -- it can hand
326 them a username and realm taken straight from the fuzzer instead of
327 the fixed constants fuzz_request has to use.
328
329 Every output buffer is a heap allocation of *exactly* the size
330 declared to MHD, and that size is swept down to zero, so a helper
331 that writes its full digest into a buffer that is too small is
332 caught immediately by ASAN's redzone rather than silently
333 corrupting an adjacent object. That is precisely the shape of the
334 overflow fixed in commit 5a73c1ae. */
335 if (0 != (sel & 0x02))
336 {
337 /* Exactly one base hashing algorithm per entry. In particular
338 MHD_DIGEST_AUTH_ALGO3_INVALID must not appear: every one of these
339 helpers routes through digest_get_hash_size(), which asserts that
340 precisely one of MD5 / SHA-256 / SHA-512-256 is named. Passing
341 INVALID is an API violation on the caller's side, not something
342 worth fuzzing. */
343 static const enum MHD_DigestAuthAlgo3 algo3s[] = {
350 };
351 enum MHD_DigestAuthAlgo3 a =
352 algo3s[(sel >> 5) % (sizeof (algo3s) / sizeof (algo3s[0]))];
353 size_t hs = MHD_digest_get_hash_size (a);
354
355 /* Split the fuzzer-supplied header value into username / realm /
356 password. Each gets its OWN exactly-sized allocation rather than
357 being carved out of `value` in place: that way each string is
358 followed by its own ASAN redzone, so a helper reading one byte
359 past the end of the username is reported precisely instead of
360 quietly running into the realm that would follow it in a shared
361 buffer. */
362 size_t o1 = vlen / 3;
363 size_t o2 = (2 * vlen) / 3;
364 char *user = fuzz_dup_n (value, o1);
365 char *realm = fuzz_dup_n (value + o1, o2 - o1);
366 char *pass = fuzz_dup_n (value + o2, vlen - o2);
367
368 if ( (NULL != user) && (NULL != realm) && (NULL != pass) )
369 {
370 if ( (0 != hs) &&
371 (hs <= 64) )
372 {
373 size_t claim = hs - (size_t) (data[0] % (unsigned int) (hs + 1u));
374 void *bin = malloc (claim);
375
376 if (NULL != bin)
377 {
378 (void) MHD_digest_auth_calc_userdigest (a, user, realm, pass,
379 bin, claim);
380 (void) MHD_digest_auth_calc_userhash (a, user, realm, bin, claim);
381 free (bin);
382 }
383 }
384 {
385 size_t need = (0 != hs) ? (2 * hs + 1) : 1;
386 size_t claim = need - (size_t) (data[0] % (unsigned int) (need + 1u));
387 char *hex = (char *) malloc (claim);
388
389 if (NULL != hex)
390 {
391 (void) MHD_digest_auth_calc_userhash_hex (a, user, realm,
392 hex, claim);
393 free (hex);
394 }
395 }
396 }
397 free (user);
398 free (realm);
399 free (pass);
400 }
401#endif /* DAUTH_SUPPORT */
402
403 MHD_pool_destroy (pool);
404 free (value);
405 return 0;
406}
407
408
409/* ------------------------------------------------------------------ */
410/* Generator */
411/* ------------------------------------------------------------------ */
412
413static const char *const gen_param_names[] = {
414 "username", "username*", "realm", "nonce", "uri", "response", "algorithm",
415 "qop", "nc", "cnonce", "opaque", "userhash", "charset", "domain",
416 "unknown", "", "USERNAME", "user name"
417};
418
419static const char *const gen_param_values[] = {
420 "\"user\"", "user", "\"\"", "\"a\\\"b\"", "\"a\\\\\"", "\"\\\"",
421 "UTF-8''a%20b", "utf-8''%41", "''", "'", "true", "false", "TRUE",
422 "auth", "auth-int", "auth,auth-int", "\"auth\"", "MD5", "SHA-256",
423 "SHA-512-256", "BOGUS", "\"SHA-256\"", "00000001", "ffffffff",
424 "0123456789abcdef0123456789abcdef",
425 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
426 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
427 "\"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
428 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\""
429};
430
431
432static size_t
434 uint8_t *buf,
435 size_t cap)
436{
437 size_t len = 0;
438 unsigned int nparams;
439 unsigned int i;
440 int basic;
441
442 if (cap < 32)
443 return 0;
444 basic = fuzz_chance (rng, 4);
445 buf[len++] = (uint8_t) ((fuzz_byte (rng) & 0xFE) | (basic ? 1u : 0u));
446
447/* NB: evaluate the argument exactly once -- it usually contains a
448 call into the PRNG. */
449#define ADD(s) \
450 do { \
451 const char *s_ = (s); \
452 size_t l_ = strlen (s_); \
453 if (len + l_ >= cap) \
454 return len; \
455 memcpy (buf + len, s_, l_); \
456 len += l_; \
457 } while (0)
458
459 if (basic)
460 {
461 unsigned int n;
462
463 ADD ("Basic ");
464 n = fuzz_below (rng, 48);
465 for (i = 0; (i < n) && (len < cap); i++)
466 buf[len++] =
467 (uint8_t) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
468 "0123456789+/= "[fuzz_below (rng, 66)];
469 return len;
470 }
471
472 ADD ("Digest ");
473 nparams = 1 + fuzz_below (rng, 12);
474 for (i = 0; i < nparams; i++)
475 {
476 if (0 != i)
477 ADD (fuzz_chance (rng, 8) ? "," : ", ");
479 (uint32_t) (sizeof (gen_param_names)
480 / sizeof (char *)))]);
481 if (! fuzz_chance (rng, 10))
482 ADD (fuzz_chance (rng, 10) ? " = " : "=");
484 (uint32_t) (sizeof (gen_param_values)
485 / sizeof (char *)))]);
486 }
487#undef ADD
488 return len;
489}
490
491
492/* ------------------------------------------------------------------ */
493/* Seed corpus */
494/* ------------------------------------------------------------------ */
495
496struct ah_seed
497{
498 const char *txt;
499 size_t len;
500};
501
502#define ASEED(t) { t, sizeof (t) - 1 }
503
504static const struct ah_seed ah_seeds[] = {
505 ASEED ("\x00" "Digest username=\"user\", realm=\"TestRealm\", "
506 "nonce=\"0123456789abcdef\", uri=\"/a\", qop=auth, nc=00000001, "
507 "cnonce=\"x\", algorithm=MD5, response=\"0123456789abcdef\""),
508 ASEED ("\x00" "Digest algorithm=BOGUS"),
509 ASEED ("\x00" "Digest algorithm="),
510 ASEED ("\x00" "Digest username*=UTF-8''a%20b"),
511 ASEED ("\x00" "Digest username*=''"),
512 ASEED ("\x00" "Digest username=\"\\\""),
513 ASEED ("\x00" "Digest userhash=true, username=\"aaaa\""),
514 ASEED ("\x00" "Digest response=\""
515 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
516 "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\""),
517 ASEED ("\x00" "Digest"),
518 ASEED ("\x00" "Digest ,,,,,"),
519 ASEED ("\x00" "Digest nc=\"ffffffffffffffffffffffff\""),
520 ASEED ("\x01" "Basic dXNlcjpwYXNz"),
521 ASEED ("\x01" "Basic "),
522 ASEED ("\x01" "Basic ===="),
523 ASEED ("\x01" "Basic QQ==QQ=="),
524
525 /* Bit 0x02 of byte 0 additionally runs the connection-less digest
526 helpers (MHD_digest_auth_calc_userdigest/_userhash/_userhash_hex)
527 with the header value split into username / realm / password. Bits
528 5-7 pick the algorithm and byte 0 also sets the deliberately
529 undersized output-buffer length, so the seeds below cover several
530 algorithms at several buffer sizes. Without a seed here the branch
531 is only reachable by a lucky bit flip in byte 0.
532
533 The three *_SESSION algorithms are not seeded separately: they hash
534 with the same primitive as their non-session counterpart, so they
535 reach no code the entries below do not, and byte 0 is the byte a
536 mutator flips first anyway. */
537 ASEED ("\x02" "user:TestRealm:pass"),
538 ASEED ("\x22" "user:TestRealm:pass"),
539 ASEED ("\x42" "user:TestRealm:pass"),
540 ASEED ("\xa2" "user:TestRealm:pass"),
541 /* Degenerate splits: empty username, empty realm, empty password. */
542 ASEED ("\x02" "::"),
543 ASEED ("\x02" ""),
544 /* Both the parser and the helpers in one execution. */
545 ASEED ("\x02" "Digest username=\"user\", realm=\"TestRealm\"")
546};
547
548
549static size_t
551{
552 return sizeof (ah_seeds) / sizeof (ah_seeds[0]);
553}
554
555
556static const uint8_t *
557fuzz_seed_get (size_t idx,
558 size_t *len)
559{
560 *len = ah_seeds[idx].len;
561 return (const uint8_t *) ah_seeds[idx].txt;
562}
static struct MHD_Daemon * get_shared_daemon(void)
static size_t fuzz_seed_count(void)
static const size_t pool_sizes[]
static void stop_shared_daemon(void)
#define ADD(s)
#define FUZZ_HARNESS_NAME
static unsigned long stat_dauth_parsed
static const uint8_t * fuzz_seed_get(size_t idx, size_t *len)
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
static const char *const gen_param_values[]
#define ASEED(t)
static size_t fuzz_generate(struct fuzz_rng *rng, uint8_t *buf, size_t cap)
static unsigned long stat_bauth_failed
static void print_stats(void)
static unsigned long stat_dauth_failed
static const struct ah_seed ah_seeds[]
static char * fuzz_dup_n(const char *src, size_t len)
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 unsigned long stat_bauth_parsed
static const char *const gen_param_names[]
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)
Declarations for HTTP authorisation general functions.
_MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userdigest(enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, const char *password, void *userdigest_bin, size_t bin_buf_size)
_MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userhash_hex(enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, char *userhash_hex, size_t hex_buf_size)
_MHD_EXTERN size_t MHD_digest_get_hash_size(enum MHD_DigestAuthAlgo3 algo3)
Definition digestauth.c:296
_MHD_EXTERN enum MHD_Result MHD_digest_auth_calc_userhash(enum MHD_DigestAuthAlgo3 algo3, const char *username, const char *realm, void *userhash_bin, size_t bin_buf_size)
_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_AUTHORIZATION
Definition microhttpd.h:584
MHD internal shared structures.
@ MHD_CONNECTION_HEADERS_PROCESSED
Definition internal.h:646
void MHD_pool_destroy(struct MemoryPool *pool)
Definition memorypool.c:361
struct MemoryPool * MHD_pool_create(size_t max)
Definition memorypool.c:290
memory pool; mostly used for efficient (de)allocation for each connection and bounding memory use for...
#define NULL
Header for string manipulating helpers.
#define MHD_STATICSTR_LEN_(macro)
@ MHD_OPTION_END
MHD_Result
Definition microhttpd.h:163
@ MHD_NO
Definition microhttpd.h:167
MHD_DigestAuthAlgo3
@ MHD_DIGEST_AUTH_ALGO3_MD5_SESSION
@ MHD_DIGEST_AUTH_ALGO3_MD5
@ MHD_DIGEST_AUTH_ALGO3_SHA256
@ MHD_DIGEST_AUTH_ALGO3_SHA512_256_SESSION
@ MHD_DIGEST_AUTH_ALGO3_SHA256_SESSION
@ MHD_DIGEST_AUTH_ALGO3_SHA512_256
void * data
@ MHD_HEADER_KIND
@ MHD_USE_ERROR_LOG
@ MHD_USE_NO_LISTEN_SOCKET
struct MHD_Request rq
Definition internal.h:1371
struct MemoryPool * pool
Definition internal.h:1386
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
struct _MHD_str_w_len token68
Definition basicauth.h:40
const char * str