GNU libmicrohttpd 1.0.9
Loading...
Searching...
No Matches
fuzz_memorypool.c File Reference

Direct in-process fuzzer for src/microhttpd/memorypool.c. More...

#include "fuzz_common.h"
#include "internal.h"
#include "memorypool.h"
Include dependency graph for fuzz_memorypool.c:

Go to the source code of this file.

Macros

#define FUZZ_HARNESS_NAME   "fuzz_memorypool"
 
#define FZ_ALIGN_SIZE   (2 * sizeof (void *))
 
#define FZ_RED_ZONE_SIZE   ((size_t) 0)
 
#define FZ_MAX_BLOCKS   24
 
#define FZ_MAX_OPS   64
 
#define FZ_PAT_MAX   96
 
#define FZ_NUM_POOL_SIZES    (sizeof (fz_pool_sizes) / sizeof (fz_pool_sizes[0]))
 
#define SZ_TINY(n)
 
#define SZ_FRAC(k)
 
#define SZ_FREE   ((uint8_t) 2u)
 
#define SZ_FREE_M(d)
 
#define SZ_FREE_P(d)
 
#define SZ_HUGE   ((uint8_t) 3u)
 
#define PS_1   0
 
#define PS_16   2
 
#define PS_32   4
 
#define PS_64   6
 
#define PS_128   8
 
#define PS_256   11
 
#define PS_512   14
 
#define PS_1024   16
 
#define PS_1500   18
 
#define PS_32768   23
 
#define OPR(o, b, s)
 
#define MSEED(a)
 

Enumerations

enum  fz_op {
  FZ_OP_ALLOC = 0 , FZ_OP_ALLOC_ALL , FZ_OP_TRY_ALLOC , FZ_OP_SQUEEZE ,
  FZ_OP_REALLOC , FZ_OP_REALLOC_NEW , FZ_OP_DEALLOC , FZ_OP_RESET ,
  FZ_OP_GET_FREE , FZ_OP_RESIZABLE , FZ_OP_VERIFY , FZ_OP_RECREATE ,
  FZ_OP_COUNT
}
 

Functions

static void print_stats (void)
 
static void fz_report (const char *op, const char *what)
 
static size_t fz_round (size_t n)
 
static uint8_t fz_pat (uint8_t tag, size_t idx)
 
static void fz_fill (uint8_t *ptr, uint8_t tag, size_t from, size_t to)
 
static void fz_verify (const uint8_t *ptr, size_t size, uint8_t tag, const char *op, const char *what)
 
static void fz_verify_all (struct fz_pool *st, const char *op)
 
static void fz_check_ptr (struct fz_pool *st, void *p, size_t size, const char *op)
 
static void fz_check_invariants (struct fz_pool *st, const char *op)
 
static struct fz_block * fz_add (struct fz_pool *st, void *ptr, size_t size, bool from_end)
 
static void fz_del (struct fz_pool *st, struct fz_block *b)
 
static struct fz_block * fz_pick (struct fz_pool *st, uint8_t sel, bool front_only)
 
static size_t fz_size (uint8_t sel, size_t cap, size_t freem)
 
static void fz_pool_close (struct fz_pool *st)
 
static bool fz_pool_open (struct fz_pool *st, uint8_t sel)
 
static void fz_op_alloc (struct fz_pool *st, const uint8_t *op, bool prefer_end)
 
static void fz_op_alloc_all (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_try_alloc (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_squeeze (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_realloc (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_realloc_new (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_dealloc (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_reset (struct fz_pool *st, const uint8_t *op)
 
static void fz_op_resizable (struct fz_pool *st, const uint8_t *op)
 
static void fz_do_op (struct fz_pool *st, const uint8_t *op, bool prefer_end)
 
int LLVMFuzzerTestOneInput (const uint8_t *data, size_t size)
 
static uint8_t gen_size_sel (struct fuzz_rng *rng)
 
static size_t fuzz_generate (struct fuzz_rng *rng, uint8_t *buf, size_t cap)
 
static size_t fuzz_seed_count (void)
 
static const uint8_t * fuzz_seed_get (size_t idx, size_t *len)
 

Variables

static const size_t fz_pool_sizes []
 
static int full_dealloc_enabled
 
static int wrap_realloc_enabled
 
static int known_bugs_read
 
static unsigned long stat_ops
 
static unsigned long stat_alloc_ok
 
static unsigned long stat_alloc_fail
 
static unsigned long stat_realloc_moved
 
static unsigned long stat_resets
 
static const uint8_t gen_ops []
 
static const uint8_t seed_grow_shrink []
 
static const uint8_t seed_two_blocks_realloc []
 
static const uint8_t seed_from_end []
 
static const uint8_t seed_fill_exactly []
 
static const uint8_t seed_squeeze []
 
static const uint8_t seed_squeeze_small []
 
static const uint8_t seed_reset_keep []
 
static const uint8_t seed_reset_full []
 
static const uint8_t seed_boundary []
 
static const uint8_t seed_wrap []
 
static const uint8_t seed_tiny_pool []
 
static const uint8_t seed_zero_sizes []
 
static const uint8_t seed_many_small []
 
static const uint8_t seed_recreate []
 
static const uint8_t seed_connection_cycle []
 
static const uint8_t seed_dealloc_end_full []
 
static const uint8_t seed_realloc_wrap []
 
static const struct mp_seed mp_seeds []
 

Detailed Description

Direct in-process fuzzer for src/microhttpd/memorypool.c.

Author
Christian Grothoff

memorypool.c is the per-connection allocator: a bump allocator that hands out "normal" blocks from the start of the pool and small, never-reallocated blocks from its end, plus an in-place resize and a reset that moves one surviving block back to the beginning. Every request buffer, every header name and value of every connection lives in it, so a single mis-computed offset here is a cross-request information leak or a heap overflow – and one that ASAN alone cannot see, because the whole pool is one malloc()ed object. (It does see it in a build where MHD_ASAN_POISON_ACTIVE is defined, i.e. where sanitizer/asan_interface.h was found and memorypool.c poisons the unallocated parts of the pool itself. The harness works either way and knows the red zone size that mode adds between two blocks.)

The harness therefore keeps its own model of the pool and checks it against the allocator after every single operation:

  • every returned pointer lies inside the pool, is aligned, and does not overlap any other live allocation;
  • every live allocation carries a position-dependent byte pattern that is re-verified later; the allocator moves data around in MHD_pool_reallocate() and MHD_pool_reset(), and the pattern is what proves the move was correct;
  • the sum of the live allocations plus MHD_pool_get_free() never exceeds the size of the pool, MHD_pool_get_free() never grows across an allocation and never shrinks across a deallocation;
  • the implications the rest of MHD relies on hold:
    • MHD_pool_allocate (pool, MHD_pool_get_free (pool), false) must succeed (response.c:2203 does exactly this), and more generally an allocation must not fail while get_free() reports room for its rounded-up size;
    • if MHD_pool_is_resizable_inplace() says yes, a reallocation that fits must succeed and must not move the block (connection.c:729/750/2082/2121 assert precisely that);
    • after freeing the number of bytes MHD_pool_try_alloc() asked for in required_bytes, the same call must succeed – this is the whole point of that out parameter and the logic of MHD_connection_alloc_memory_().

Documented preconditions are respected, since violating them would only produce findings about the harness: blocks allocated "from the end" are never passed to MHD_pool_reallocate(), MHD_pool_reset() is never asked for more than the pool size nor for more copy_bytes than the kept block holds, and a block is only ever deallocated with the size it was allocated with.

Two open defects of memorypool.c are reachable and would otherwise make every run abort; each is gated behind an environment variable, documented in full at full_dealloc_enabled and wrap_realloc_enabled below and in README section 6.1, with a byte-exact reproducer in the built-in seed corpus. Nothing else is gated.

Input format: byte 0 pool size selector (index into fz_pool_sizes[]) byte 1 flags 0x01 verify every live block after every operation 0x02 prefer allocating "from the end" rest unused byte 2.. a stream of 3-byte operation records [0] opcode (see enum fz_op) [1] block selector / auxiliary nibbles [2] size selector (see fz_size())

Definition in file fuzz_memorypool.c.

Macro Definition Documentation

◆ FUZZ_HARNESS_NAME

#define FUZZ_HARNESS_NAME   "fuzz_memorypool"

Definition at line 88 of file fuzz_memorypool.c.

Referenced by print_stats().

◆ FZ_ALIGN_SIZE

#define FZ_ALIGN_SIZE   (2 * sizeof (void *))

Alignment of every block the pool hands out; memorypool.c aligns to two words "as GNU libc does".

Definition at line 101 of file fuzz_memorypool.c.

Referenced by fz_check_ptr(), fz_op_realloc(), and fz_round().

◆ FZ_MAX_BLOCKS

#define FZ_MAX_BLOCKS   24

Maximum number of live allocations the model tracks. An operation that would exceed this is skipped, so the model never loses a block.

Definition at line 118 of file fuzz_memorypool.c.

Referenced by fz_op_alloc(), fz_op_alloc_all(), fz_op_realloc_new(), fz_op_squeeze(), and fz_op_try_alloc().

◆ FZ_MAX_OPS

#define FZ_MAX_OPS   64

Maximum number of operations executed for one input.

Definition at line 123 of file fuzz_memorypool.c.

Referenced by fuzz_generate(), and LLVMFuzzerTestOneInput().

◆ FZ_NUM_POOL_SIZES

#define FZ_NUM_POOL_SIZES    (sizeof (fz_pool_sizes) / sizeof (fz_pool_sizes[0]))

Definition at line 150 of file fuzz_memorypool.c.

Referenced by fuzz_generate(), and fz_pool_open().

◆ FZ_PAT_MAX

#define FZ_PAT_MAX   96

Number of bytes of each allocation that carry the check pattern. Bounding this keeps the harness fast; the interesting bugs of a bump allocator are all at the start of a block or at its (ASAN-guarded) end.

Definition at line 131 of file fuzz_memorypool.c.

Referenced by fz_fill(), and fz_verify().

◆ FZ_RED_ZONE_SIZE

#define FZ_RED_ZONE_SIZE   ((size_t) 0)

Size of the red zone memorypool.c keeps between two blocks. It only exists in a build with user memory poisoning, i.e. when the pool itself is instrumented; without it the pool is packed tight.

Definition at line 111 of file fuzz_memorypool.c.

Referenced by fz_pool_open().

◆ MSEED

#define MSEED ( a)
Value:
{ (a), sizeof (a) }

Definition at line 1577 of file fuzz_memorypool.c.

◆ OPR

#define OPR ( o,
b,
s )
Value:
((uint8_t) (o)), ((uint8_t) (b)), (s)

Definition at line 1385 of file fuzz_memorypool.c.

◆ PS_1

#define PS_1   0

Definition at line 1374 of file fuzz_memorypool.c.

◆ PS_1024

#define PS_1024   16

Definition at line 1381 of file fuzz_memorypool.c.

◆ PS_128

#define PS_128   8

Definition at line 1378 of file fuzz_memorypool.c.

◆ PS_1500

#define PS_1500   18

Definition at line 1382 of file fuzz_memorypool.c.

◆ PS_16

#define PS_16   2

Definition at line 1375 of file fuzz_memorypool.c.

◆ PS_256

#define PS_256   11

Definition at line 1379 of file fuzz_memorypool.c.

◆ PS_32

#define PS_32   4

Definition at line 1376 of file fuzz_memorypool.c.

◆ PS_32768

#define PS_32768   23

Definition at line 1383 of file fuzz_memorypool.c.

◆ PS_512

#define PS_512   14

Definition at line 1380 of file fuzz_memorypool.c.

◆ PS_64

#define PS_64   6

Definition at line 1377 of file fuzz_memorypool.c.

◆ SZ_FRAC

#define SZ_FRAC ( k)
Value:
((uint8_t) ((((unsigned int) (k)) << 2) | 1u))

Definition at line 1367 of file fuzz_memorypool.c.

◆ SZ_FREE

#define SZ_FREE   ((uint8_t) 2u)

Definition at line 1368 of file fuzz_memorypool.c.

◆ SZ_FREE_M

#define SZ_FREE_M ( d)
Value:
((uint8_t) ((((unsigned int) (d)) << 2) | 2u))

Definition at line 1369 of file fuzz_memorypool.c.

◆ SZ_FREE_P

#define SZ_FREE_P ( d)
Value:
((uint8_t) ((((unsigned int) (d)) << 2) | 0x20u | 2u))

Definition at line 1370 of file fuzz_memorypool.c.

◆ SZ_HUGE

#define SZ_HUGE   ((uint8_t) 3u)

Definition at line 1371 of file fuzz_memorypool.c.

◆ SZ_TINY

#define SZ_TINY ( n)
Value:
((uint8_t) ((((unsigned int) (n)) << 2) | 0u))

Definition at line 1366 of file fuzz_memorypool.c.

Enumeration Type Documentation

◆ fz_op

enum fz_op
Enumerator
FZ_OP_ALLOC 

MHD_pool_allocate()

FZ_OP_ALLOC_ALL 

allocate exactly MHD_pool_get_free() bytes

FZ_OP_TRY_ALLOC 

MHD_pool_try_alloc()

FZ_OP_SQUEEZE 

try_alloc + shrink + retry

FZ_OP_REALLOC 

MHD_pool_reallocate() of a live block

FZ_OP_REALLOC_NEW 

MHD_pool_reallocate() with old == NULL

FZ_OP_DEALLOC 

MHD_pool_deallocate()

FZ_OP_RESET 

MHD_pool_reset()

FZ_OP_GET_FREE 

MHD_pool_get_free()

FZ_OP_RESIZABLE 

MHD_pool_is_resizable_inplace()

FZ_OP_VERIFY 

re-check every live block

FZ_OP_RECREATE 

MHD_pool_destroy() + MHD_pool_create()

FZ_OP_COUNT 

Definition at line 154 of file fuzz_memorypool.c.

Function Documentation

◆ fuzz_generate()

static size_t fuzz_generate ( struct fuzz_rng * rng,
uint8_t * buf,
size_t cap )
static

Definition at line 1323 of file fuzz_memorypool.c.

References fuzz_below(), fuzz_byte(), fuzz_chance(), FZ_MAX_OPS, FZ_NUM_POOL_SIZES, gen_ops, and gen_size_sel().

Here is the call graph for this function:

◆ fuzz_seed_count()

static size_t fuzz_seed_count ( void )
static

Definition at line 1601 of file fuzz_memorypool.c.

References mp_seeds.

◆ fuzz_seed_get()

static const uint8_t * fuzz_seed_get ( size_t idx,
size_t * len )
static

Definition at line 1608 of file fuzz_memorypool.c.

References mp_seeds.

◆ fz_add()

static struct fz_block * fz_add ( struct fz_pool * st,
void * ptr,
size_t size,
bool from_end )
static

Definition at line 527 of file fuzz_memorypool.c.

References fz_fill().

Referenced by fz_op_alloc(), fz_op_alloc_all(), fz_op_realloc_new(), fz_op_reset(), fz_op_squeeze(), and fz_op_try_alloc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_check_invariants()

static void fz_check_invariants ( struct fz_pool * st,
const char * op )
static

Invariants that must hold after every single operation.

Definition at line 503 of file fuzz_memorypool.c.

References fz_report(), fz_verify_all(), and MHD_pool_get_free().

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_check_ptr()

static void fz_check_ptr ( struct fz_pool * st,
void * p,
size_t size,
const char * op )
static

Check a pointer the pool just returned: inside the pool, aligned, and not overlapping any block that is still live. The caller has already removed the block being replaced (if any) from the model.

Definition at line 460 of file fuzz_memorypool.c.

References FZ_ALIGN_SIZE, and fz_report().

Referenced by fz_op_alloc(), fz_op_alloc_all(), fz_op_realloc(), fz_op_realloc_new(), fz_op_reset(), fz_op_squeeze(), and fz_op_try_alloc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_del()

static void fz_del ( struct fz_pool * st,
struct fz_block * b )
static

Definition at line 544 of file fuzz_memorypool.c.

Referenced by fz_op_dealloc(), and fz_op_realloc().

Here is the caller graph for this function:

◆ fz_do_op()

static void fz_do_op ( struct fz_pool * st,
const uint8_t * op,
bool prefer_end )
static

◆ fz_fill()

static void fz_fill ( uint8_t * ptr,
uint8_t tag,
size_t from,
size_t to )
static

Write the check pattern into ptr[from..to).

Definition at line 392 of file fuzz_memorypool.c.

References fz_pat(), and FZ_PAT_MAX.

Referenced by fz_add(), and fz_op_realloc().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_alloc()

static void fz_op_alloc ( struct fz_pool * st,
const uint8_t * op,
bool prefer_end )
static

MHD_pool_allocate(), from the front or from the end.

Definition at line 674 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), FZ_MAX_BLOCKS, fz_report(), fz_round(), fz_size(), MHD_pool_allocate(), MHD_pool_get_free(), NULL, stat_alloc_fail, and stat_alloc_ok.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_alloc_all()

static void fz_op_alloc_all ( struct fz_pool * st,
const uint8_t * op )
static

Allocate exactly MHD_pool_get_free() bytes. response.c:2203 does this and dereferences the result without a NULL check, so it must always succeed, and it must leave the pool without any free space.

Definition at line 722 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), FZ_MAX_BLOCKS, fz_report(), MHD_pool_allocate(), MHD_pool_get_free(), NULL, stat_alloc_fail, and stat_alloc_ok.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_dealloc()

static void fz_op_dealloc ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_deallocate() of a live block, or of NULL ("the NULL is tolerated"). A block is always deallocated with the size it currently has: memorypool.c asserts that the block lies inside the allocated area, and splitting a block is explicitly disallowed.

Definition at line 1013 of file fuzz_memorypool.c.

References full_dealloc_enabled, fz_del(), fz_pick(), fz_report(), fz_verify(), MHD_pool_deallocate(), MHD_pool_get_free(), and NULL.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_realloc()

static void fz_op_realloc ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_reallocate() of a live block that was allocated from the front. Blocks allocated "from the end" are excluded: memorypool.c documents ("Blocks 'from the end' must not be reallocated") and asserts that.

Definition at line 900 of file fuzz_memorypool.c.

References FZ_ALIGN_SIZE, fz_check_ptr(), fz_del(), fz_fill(), fz_pick(), fz_report(), fz_size(), fz_verify(), MHD_pool_get_free(), MHD_pool_is_resizable_inplace(), MHD_pool_reallocate(), NULL, SIZE_MAX, stat_realloc_moved, and wrap_realloc_enabled.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_realloc_new()

static void fz_op_realloc_new ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_reallocate() with old == NULL, which is the documented way to obtain a fresh relocatable block (connection.c:2121 uses it for the very first write buffer).

Definition at line 975 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), FZ_MAX_BLOCKS, fz_report(), fz_round(), fz_size(), MHD_pool_get_free(), MHD_pool_reallocate(), NULL, stat_alloc_fail, and stat_alloc_ok.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_reset()

static void fz_op_reset ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_reset(). Preconditions: copy_bytes <= new_size, new_size <= size of the pool, and the kept block must really hold copy_bytes bytes.

Definition at line 1071 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), fz_pick(), fz_report(), fz_size(), fz_verify(), MHD_pool_get_free(), MHD_pool_reset(), NULL, and stat_resets.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_resizable()

static void fz_op_resizable ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_is_resizable_inplace() on a live block, or on NULL.

Definition at line 1120 of file fuzz_memorypool.c.

References fz_pick(), fz_report(), MHD_pool_is_resizable_inplace(), MHD_pool_reallocate(), and NULL.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_squeeze()

static void fz_op_squeeze ( struct fz_pool * st,
const uint8_t * op )
static

The logic of MHD_connection_alloc_memory_() (connection.c:706): when MHD_pool_try_alloc() fails it names the number of bytes that have to be freed in the relocatable area; freeing exactly that many bytes by shrinking a block that is resizable in-place must make the very same allocation succeed. If it does not, MHD returns "out of memory" for a request it has the memory for.

Definition at line 820 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), FZ_MAX_BLOCKS, fz_report(), fz_size(), fz_verify(), MHD_pool_get_free(), MHD_pool_is_resizable_inplace(), MHD_pool_reallocate(), MHD_pool_try_alloc(), NULL, and SIZE_MAX.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_op_try_alloc()

static void fz_op_try_alloc ( struct fz_pool * st,
const uint8_t * op )
static

MHD_pool_try_alloc(). Checks the contract of required_bytes.

Definition at line 757 of file fuzz_memorypool.c.

References fz_add(), fz_check_ptr(), FZ_MAX_BLOCKS, fz_report(), fz_round(), fz_size(), MHD_pool_get_free(), MHD_pool_try_alloc(), NULL, SIZE_MAX, stat_alloc_fail, and stat_alloc_ok.

Referenced by fz_do_op().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_pat()

static uint8_t fz_pat ( uint8_t tag,
size_t idx )
static

Byte idx of the check pattern of a block with seed tag.

Definition at line 379 of file fuzz_memorypool.c.

Referenced by fz_fill(), and fz_verify().

Here is the caller graph for this function:

◆ fz_pick()

static struct fz_block * fz_pick ( struct fz_pool * st,
uint8_t sel,
bool front_only )
static

Pick a live block.

Parameters
stthe model
selselector byte
front_onlyonly consider blocks that may be reallocated
Returns
NULL if there is no such block

Definition at line 560 of file fuzz_memorypool.c.

References NULL.

Referenced by fz_op_dealloc(), fz_op_realloc(), fz_op_reset(), and fz_op_resizable().

Here is the caller graph for this function:

◆ fz_pool_close()

static void fz_pool_close ( struct fz_pool * st)
static

Definition at line 622 of file fuzz_memorypool.c.

References MHD_pool_destroy(), and NULL.

Referenced by fz_do_op(), fz_pool_open(), and LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_pool_open()

static bool fz_pool_open ( struct fz_pool * st,
uint8_t sel )
static

Create a pool and learn its base address and its exact size.

Returns
false if the pool could not be created

Definition at line 636 of file fuzz_memorypool.c.

References FZ_NUM_POOL_SIZES, fz_pool_close(), fz_pool_sizes, FZ_RED_ZONE_SIZE, fz_report(), fz_round(), MHD_pool_create(), MHD_pool_get_free(), MHD_pool_reset(), and NULL.

Referenced by fz_do_op(), and LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_report()

static void fz_report ( const char * op,
const char * what )
static

Report a finding, prefixed with the name of the operation that was running.

Definition at line 353 of file fuzz_memorypool.c.

References fuzz_report_finding().

Referenced by fz_check_invariants(), fz_check_ptr(), fz_op_alloc(), fz_op_alloc_all(), fz_op_dealloc(), fz_op_realloc(), fz_op_realloc_new(), fz_op_reset(), fz_op_resizable(), fz_op_squeeze(), fz_op_try_alloc(), fz_pool_open(), and fz_verify().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_round()

static size_t fz_round ( size_t n)
static

The ROUND_TO_ALIGN() of memorypool.c, wrap-around included: a size that is too close to SIZE_MAX rounds to 0, which is how the allocator detects it.

Definition at line 369 of file fuzz_memorypool.c.

References FZ_ALIGN_SIZE.

Referenced by fz_op_alloc(), fz_op_realloc_new(), fz_op_try_alloc(), and fz_pool_open().

Here is the caller graph for this function:

◆ fz_size()

static size_t fz_size ( uint8_t sel,
size_t cap,
size_t freem )
static

Turn a selector byte into a size. The four classes matter: tiny sizes are what headers use, fractions of the pool are what the request buffers use, sizes around the currently free amount are where the off-by-one-block bugs live, and sizes close to SIZE_MAX exercise the "value wrap" guards of the allocator.

Definition at line 598 of file fuzz_memorypool.c.

References SIZE_MAX.

Referenced by fz_op_alloc(), fz_op_realloc(), fz_op_realloc_new(), fz_op_reset(), fz_op_squeeze(), and fz_op_try_alloc().

Here is the caller graph for this function:

◆ fz_verify()

static void fz_verify ( const uint8_t * ptr,
size_t size,
uint8_t tag,
const char * op,
const char * what )
static

Verify that the first bytes of ptr still carry the pattern of tag. The expected bytes are built in an exactly sized malloc()ed buffer, so that a comparison that runs off either end is caught by ASAN rather than silently passing.

Definition at line 413 of file fuzz_memorypool.c.

References fz_pat(), FZ_PAT_MAX, fz_report(), and NULL.

Referenced by fz_op_dealloc(), fz_op_realloc(), fz_op_reset(), fz_op_squeeze(), and fz_verify_all().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fz_verify_all()

static void fz_verify_all ( struct fz_pool * st,
const char * op )
static

Verify every live block.

Definition at line 440 of file fuzz_memorypool.c.

References fz_verify().

Referenced by fz_check_invariants(), fz_do_op(), and LLVMFuzzerTestOneInput().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_size_sel()

static uint8_t gen_size_sel ( struct fuzz_rng * rng)
static

Size selector bytes that are worth trying often: the boundary cases (exactly the free space, one alignment unit more or less) and the value-wrap guards.

Definition at line 1298 of file fuzz_memorypool.c.

References fuzz_below().

Referenced by fuzz_generate().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ LLVMFuzzerTestOneInput()

int LLVMFuzzerTestOneInput ( const uint8_t * data,
size_t size )

The fuzz target. Signature is the libFuzzer one on purpose, so that the very same harness can be linked with libFuzzer or AFL++ later.

< see queue_unprocessed_conn()

Definition at line 1217 of file fuzz_memorypool.c.

References data, full_dealloc_enabled, fz_do_op(), FZ_MAX_OPS, fz_pool_close(), fz_pool_open(), fz_verify_all(), known_bugs_read, MHD_init_mem_pools_(), NULL, print_stats(), and wrap_realloc_enabled.

Here is the call graph for this function:

◆ print_stats()

static void print_stats ( void )
static

Definition at line 335 of file fuzz_memorypool.c.

References FUZZ_HARNESS_NAME, fuzz_verbose, stat_alloc_fail, stat_alloc_ok, stat_ops, stat_realloc_moved, and stat_resets.

Referenced by LLVMFuzzerTestOneInput().

Here is the caller graph for this function:

Variable Documentation

◆ full_dealloc_enabled

int full_dealloc_enabled
static

F1 – MHD_pool_deallocate() of a "from the end" block of a pool that is exactly full. This is a live defect in memorypool.c and is therefore off by default; set MHD_FUZZ_POOL_FULL_DEALLOC=1 to reach it (seed fuzz_memorypool-15.bin is the byte-exact reproducer).

MHD_pool_deallocate() decides whether a block came from the front or from the end with

if (block_offset <= pool->pos)     memorypool.c:666

which is ambiguous when pool->pos == pool->end, i.e. when the pool has no free space left: a "from the end" block then starts exactly at pool->pos and is treated as a front block. With –enable-asserts

mhd_assert ((block_offset != pool->pos) || (block_size == 0));   :655
mhd_assert (alg_end <= pool->pos);                               :671

both fail, i.e. the process aborts; without them the block is simply not returned to the pool (pool->end is left alone), so the memory is lost until the pool is reset or destroyed. Reproducer:

p = MHD_pool_create (128);
e = MHD_pool_allocate (p, 16, true);
(void) MHD_pool_allocate (p, MHD_pool_get_free (p), false);
MHD_pool_deallocate (p, e, 16);      <- abort / 16 bytes lost

Nothing in memorypool.h forbids this; MHD_pool_deallocate() documents only that "the NULL is tolerated" and has an explicit branch for blocks allocated from the end. MHD's own call sites never hit it, because the three MHD_pool_deallocate() calls in connection.c and response.c all pass the read or the write buffer, which are front allocations – so this is a latent defect of a supported branch of the API rather than something reachable from the network today. A dispatch on block_offset < pool->end would resolve the ambiguity.

Definition at line 270 of file fuzz_memorypool.c.

Referenced by fz_op_dealloc(), and LLVMFuzzerTestOneInput().

◆ fz_pool_sizes

const size_t fz_pool_sizes[]
static
Initial value:
= {
1, 15, 16, 17, 32, 48, 64, 96, 128, 129, 192, 256, 320, 384, 512,
768, 1024, 1400, 1500, 2048, 4096, 8192, 16384, 32768, 65536, 131072
}

Pool sizes to choose from. MHD uses 128..32768 in practice (the default is 32768 and MHD_OPTION_CONNECTION_MEMORY_LIMIT can set anything); everything at or below 32k is malloc()ed by MHD_pool_create(), so the harness knows the exact usable size and ASAN's redzone sits right behind the pool. The last two entries are larger than that on purpose: they take the mmap() path of MHD_pool_create() and the munmap() path of MHD_pool_destroy(). Both are powers of two and therefore a multiple of any page size, so the resulting pool is exactly that large either way and the check below stays exact.

Definition at line 145 of file fuzz_memorypool.c.

Referenced by fz_pool_open().

◆ gen_ops

const uint8_t gen_ops[]
static
Initial value:

Opcodes, weighted: the allocating operations and the reallocation are what move the pool's internal offsets around, so they get most of the probability mass, while the read-only queries and the destructive reset and re-create are rarer.

Definition at line 1276 of file fuzz_memorypool.c.

Referenced by fuzz_generate().

◆ known_bugs_read

int known_bugs_read
static

Definition at line 324 of file fuzz_memorypool.c.

Referenced by LLVMFuzzerTestOneInput().

◆ mp_seeds

const struct mp_seed mp_seeds[]
static
Initial value:
= {
}
static const uint8_t seed_squeeze[]
static const uint8_t seed_reset_keep[]
static const uint8_t seed_tiny_pool[]
static const uint8_t seed_boundary[]
static const uint8_t seed_recreate[]
static const uint8_t seed_wrap[]
static const uint8_t seed_dealloc_end_full[]
static const uint8_t seed_from_end[]
static const uint8_t seed_two_blocks_realloc[]
static const uint8_t seed_connection_cycle[]
static const uint8_t seed_reset_full[]
#define MSEED(a)
static const uint8_t seed_zero_sizes[]
static const uint8_t seed_many_small[]
static const uint8_t seed_squeeze_small[]
static const uint8_t seed_grow_shrink[]
static const uint8_t seed_fill_exactly[]

Definition at line 1579 of file fuzz_memorypool.c.

Referenced by fuzz_seed_count(), and fuzz_seed_get().

◆ seed_boundary

const uint8_t seed_boundary[]
static
Initial value:
= {
PS_64, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_FREE_M (1)),
OPR (FZ_OP_ALLOC, 0x01, SZ_FREE_P (1)),
OPR (FZ_OP_DEALLOC, 0x00, SZ_TINY (0)),
}
#define SZ_FREE_P(d)
#define SZ_FREE
#define OPR(o, b, s)
#define SZ_TINY(n)
#define PS_64
#define SZ_FREE_M(d)

Definition at line 1463 of file fuzz_memorypool.c.

◆ seed_connection_cycle

const uint8_t seed_connection_cycle[]
static
Initial value:
= {
PS_1500, 0x01,
OPR (FZ_OP_REALLOC_NEW, 0x00, SZ_FRAC (31)),
OPR (FZ_OP_TRY_ALLOC, 0x00, SZ_TINY (12)),
OPR (FZ_OP_TRY_ALLOC, 0x00, SZ_TINY (20)),
OPR (FZ_OP_SQUEEZE, 0x00, SZ_TINY (40)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (60)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
OPR (FZ_OP_RESET, 0xF0, SZ_FRAC (31)),
}
#define PS_1500
#define SZ_FRAC(k)

Definition at line 1533 of file fuzz_memorypool.c.

◆ seed_dealloc_end_full

const uint8_t seed_dealloc_end_full[]
static
Initial value:
= {
PS_128, 0x01,
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (16)),
OPR (FZ_OP_ALLOC_ALL, 0x00, SZ_TINY (0)),
}
#define PS_128

Definition at line 1552 of file fuzz_memorypool.c.

◆ seed_fill_exactly

const uint8_t seed_fill_exactly[]
static
Initial value:
= {
PS_128, 0x01,
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (16)),
OPR (FZ_OP_ALLOC_ALL, 0x00, SZ_TINY (0)),
OPR (FZ_OP_GET_FREE, 0x00, SZ_TINY (0)),
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (1)),
}

Definition at line 1419 of file fuzz_memorypool.c.

◆ seed_from_end

const uint8_t seed_from_end[]
static
Initial value:
= {
PS_128, 0x01,
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (16)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (1)),
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (16)),
OPR (FZ_OP_RESIZABLE, 0x00, SZ_TINY (0)),
OPR (FZ_OP_DEALLOC, 0x00, SZ_TINY (0)),
}

Definition at line 1409 of file fuzz_memorypool.c.

◆ seed_grow_shrink

const uint8_t seed_grow_shrink[]
static
Initial value:
= {
PS_512, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (32)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (63)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (8)),
OPR (FZ_OP_REALLOC, 0x00, SZ_FRAC (32)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (1)),
}
#define PS_512

Definition at line 1387 of file fuzz_memorypool.c.

◆ seed_many_small

const uint8_t seed_many_small[]
static
Initial value:
= {
PS_1500, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (5)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (5)),
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (5)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (5)),
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (5)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (5)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (33)),
OPR (FZ_OP_REALLOC, 0x01, SZ_TINY (33)),
OPR (FZ_OP_DEALLOC, 0x02, SZ_TINY (0)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
}

Definition at line 1503 of file fuzz_memorypool.c.

◆ seed_realloc_wrap

const uint8_t seed_realloc_wrap[]
static
Initial value:
= {
PS_1024, 0x01,
OPR (FZ_OP_RESET, 0x00, SZ_TINY (0)),
}
#define PS_1024

Definition at line 1564 of file fuzz_memorypool.c.

◆ seed_recreate

const uint8_t seed_recreate[]
static
Initial value:
= {
PS_32768, 0x00,
OPR (FZ_OP_ALLOC, 0x00, SZ_FRAC (60)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (24)),
}
#define PS_32768
#define PS_16

Definition at line 1518 of file fuzz_memorypool.c.

◆ seed_reset_full

const uint8_t seed_reset_full[]
static
Initial value:
= {
PS_256, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_FRAC (63)),
OPR (FZ_OP_RESET, 0xF0, SZ_FRAC (63)),
OPR (FZ_OP_RESET, 0xF0, SZ_FREE_P (1)),
}
#define PS_256

Definition at line 1455 of file fuzz_memorypool.c.

◆ seed_reset_keep

const uint8_t seed_reset_keep[]
static
Initial value:
= {
PS_1500, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (60)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (12)),
OPR (FZ_OP_RESET, 0xF0, SZ_FRAC (31)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
OPR (FZ_OP_RESET, 0xF0, SZ_TINY (4)),
}

Definition at line 1445 of file fuzz_memorypool.c.

◆ seed_squeeze

const uint8_t seed_squeeze[]
static
Initial value:
= {
PS_512, 0x01,
OPR (FZ_OP_SQUEEZE, 0x00, SZ_TINY (40)),
OPR (FZ_OP_SQUEEZE, 0x00, SZ_TINY (7)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
}

Definition at line 1428 of file fuzz_memorypool.c.

◆ seed_squeeze_small

const uint8_t seed_squeeze_small[]
static
Initial value:
= {
PS_128, 0x01,
OPR (FZ_OP_SQUEEZE, 0x00, SZ_TINY (17)),
OPR (FZ_OP_SQUEEZE, 0x00, SZ_TINY (17)),
}

Definition at line 1437 of file fuzz_memorypool.c.

◆ seed_tiny_pool

const uint8_t seed_tiny_pool[]
static
Initial value:
= {
PS_1, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (1)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (1)),
OPR (FZ_OP_GET_FREE, 0x00, SZ_TINY (0)),
OPR (FZ_OP_RESET, 0xF0, SZ_TINY (1)),
}
#define PS_1

Definition at line 1484 of file fuzz_memorypool.c.

◆ seed_two_blocks_realloc

const uint8_t seed_two_blocks_realloc[]
static
Initial value:
= {
PS_256, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (17)),
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (23)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (60)),
OPR (FZ_OP_VERIFY, 0x00, SZ_TINY (0)),
OPR (FZ_OP_REALLOC, 0x01, SZ_TINY (40)),
}

Definition at line 1398 of file fuzz_memorypool.c.

◆ seed_wrap

const uint8_t seed_wrap[]
static
Initial value:
= {
PS_256, 0x00,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (16)),
}
#define SZ_HUGE

Definition at line 1473 of file fuzz_memorypool.c.

◆ seed_zero_sizes

const uint8_t seed_zero_sizes[]
static
Initial value:
= {
PS_32, 0x01,
OPR (FZ_OP_ALLOC, 0x00, SZ_TINY (0)),
OPR (FZ_OP_ALLOC, 0x01, SZ_TINY (0)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (0)),
OPR (FZ_OP_REALLOC, 0x00, SZ_TINY (4)),
OPR (FZ_OP_DEALLOC, 0x00, SZ_TINY (0)),
}
#define PS_32

Definition at line 1493 of file fuzz_memorypool.c.

◆ stat_alloc_fail

unsigned long stat_alloc_fail
static

◆ stat_alloc_ok

unsigned long stat_alloc_ok
static

◆ stat_ops

unsigned long stat_ops
static

Statistics, printed at exit with –verbose.

Definition at line 327 of file fuzz_memorypool.c.

Referenced by fz_do_op(), and print_stats().

◆ stat_realloc_moved

unsigned long stat_realloc_moved
static

Definition at line 330 of file fuzz_memorypool.c.

Referenced by fz_op_realloc(), and print_stats().

◆ stat_resets

unsigned long stat_resets
static

Definition at line 331 of file fuzz_memorypool.c.

Referenced by fz_op_reset(), and print_stats().

◆ wrap_realloc_enabled

int wrap_realloc_enabled
static

F2 – MHD_pool_reallocate() reports success for a new_size that wrapped. Also a live defect, off by default; set MHD_FUZZ_POOL_WRAP_REALLOC=1 to reach it (seed fuzz_memorypool-16.bin is the byte-exact reproducer).

Every other entry point of memorypool.c detects a size that is too close to SIZE_MAX explicitly:

asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size);
if ( (0 == asize) && (0 != size) )
  return NULL;                 // "size too close to SIZE_MAX"

MHD_pool_allocate(), MHD_pool_try_alloc() and the "need to allocate a new block" tail of MHD_pool_reallocate() all do it. The in-place branch of MHD_pool_reallocate() instead relies on

const size_t new_apos =
  ROUND_TO_ALIGN_PLUS_RED_ZONE (old_offset + new_size);
if ( (new_apos > pool->end) ||
     (new_apos < pool->pos) )  // "Value wrap"
  return NULL;

which does not catch the wrap when the wrapped value happens to land on pool->pos. For a zero-length block sitting at the front boundary (old_size == 0, old_offset == pool->pos, which is exactly what MHD_pool_reset (pool, NULL, 0, 0) returns – see connection.c:3021 – or MHD_pool_allocate (pool, 0, false)) and any new_size in [SIZE_MAX - 14, SIZE_MAX], old_offset + new_size wraps to old_offset - 1 - k, which rounds back up to exactly old_offset, i.e. to pool->pos: neither test fires, pool->pos is left alone and the function returns old, i.e. it reports success.

p = MHD_pool_create (1024);
b = MHD_pool_reset (p, NULL, 0, 0);
r = MHD_pool_reallocate (p, b, 0, SIZE_MAX - 4);
// r == b, not NULL: the caller now believes it owns
// SIZE_MAX - 4 bytes at the start of a 1024 byte pool,
// while MHD_pool_get_free() still reports the full 1024.

The documented contract is "@return new address of the block, or NULL if the pool cannot support @a new_size bytes", so this is a plain contract violation, and one that hands the caller a buffer size the allocation does not have. No MHD call site can reach it today: every new_size MHD passes is derived from the pool size (connection.c:1636, 2031, 2082, 2121, 2170, 6935), so it is a latent defect rather than something reachable from the network. Checking the addition itself, e.g. if (new_size > pool->size - old_offset) return NULL;, would close it.

Definition at line 322 of file fuzz_memorypool.c.

Referenced by fz_op_realloc(), and LLVMFuzzerTestOneInput().