libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 669daf7a4690b106d323bc40d175ab702405095a
parent 75549625f0ba6aa56ffa4e84eedb53b5320f449f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 29 Jul 2026 13:19:23 +0200

fix red-zone support in memorypool

Diffstat:
Msrc/microhttpd/memorypool.c | 49++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 42 insertions(+), 7 deletions(-)

diff --git a/src/microhttpd/memorypool.c b/src/microhttpd/memorypool.c @@ -96,6 +96,25 @@ #define ROUND_TO_ALIGN(n) (((n) + (ALIGN_SIZE - 1)) \ / (ALIGN_SIZE) *(ALIGN_SIZE)) +/* ROUND_TO_ALIGN_PLUS_RED_ZONE() rounds up and, when user poisoning is + active, adds a red zone, so its result is always >= its argument -- + unless it wrapped. Every caller that passes a caller-supplied size + must therefore reject the result with + + if (asize < size) + + and not by testing for zero. Testing for zero is correct only in the + build where the red zone is 0: ROUND_TO_ALIGN() lands exactly on 0 for + the top ALIGN_SIZE-1 values of size_t, so a zero result is the only + wrapped outcome there. Adding a red zone moves that outcome to + _MHD_RED_ZONE_SIZE, which is non-zero and small enough to pass the + remaining-space test below it, so a zero test silently stops firing in + exactly the build that has the extra instrumentation. Sizes that are + already bounded by pool->size (MHD_pool_reset(), the block_offset + + block_size expressions) cannot wrap and need no such test -- but they + do need the trailing red zone clamped to pool->size, see the two sites + that do so. */ + #ifndef MHD_ASAN_POISON_ACTIVE #define _MHD_NOSANITIZE_PTRS /**/ @@ -408,8 +427,8 @@ MHD_pool_allocate (struct MemoryPool *pool, mhd_assert (pool->size >= pool->end - pool->pos); mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size); - if ( (0 == asize) && (0 != size) ) - return NULL; /* size too close to SIZE_MAX */ + if (asize < size) + return NULL; /* Value wrap, @a size is too close to SIZE_MAX */ if (asize > pool->end - pool->pos) return NULL; if (from_end) @@ -490,8 +509,8 @@ MHD_pool_try_alloc (struct MemoryPool *pool, mhd_assert (pool->size >= pool->end - pool->pos); mhd_assert (pool->pos == ROUND_TO_ALIGN (pool->pos)); asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (size); - if ( (0 == asize) && (0 != size) ) - { /* size is too close to SIZE_MAX, very unlikely */ + if (asize < size) + { /* Value wrap, @a size is too close to SIZE_MAX, very unlikely */ *required_bytes = SIZE_MAX; return NULL; } @@ -623,8 +642,7 @@ MHD_pool_reallocate (struct MemoryPool *pool, } /* Need to allocate new block */ asize = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size); - if ( ( (0 == asize) && - (0 != new_size) ) || /* Value wrap, too large new_size. */ + if ( (asize < new_size) || /* Value wrap, too large new_size. */ (asize > pool->end - pool->pos) ) /* Not enough space */ return NULL; @@ -695,8 +713,15 @@ MHD_pool_deallocate (struct MemoryPool *pool, exactly full the two are equal, and a block allocated "from the end" then also satisfies 'block_offset <= pool->pos', so it would be mistaken for a normal block and never returned. */ - const size_t alg_end = + size_t alg_end = ROUND_TO_ALIGN_PLUS_RED_ZONE (block_offset + block_size); + /* Clamped exactly as MHD_pool_reset() clamps pool->pos, and for the + same reason: a block that reaches the end of the pool has no room + for a trailing red zone, so its recorded end is pool->size. The + two have to agree or the "is this the last block" test below can + never match such a block. */ + if (alg_end > pool->size) + alg_end = pool->size; mhd_assert (alg_end <= pool->pos); if (alg_end == pool->pos) { @@ -834,6 +859,16 @@ MHD_pool_reset (struct MemoryPool *pool, to_zero); } pool->pos = ROUND_TO_ALIGN_PLUS_RED_ZONE (new_size); + /* The red zone is a gap kept *inside* the pool so that an overrun of the + block lands in poisoned bytes. There is no room for it once the kept + block reaches the end of the pool, and none is needed there either -- + past pool->size the allocation's own ASAN red zone takes over. Without + this clamp pool->pos would be set past pool->end for any @a new_size + within a red zone of pool->size, breaking the pool->end >= pool->pos + invariant that every other entry point asserts. In the build with no + user poisoning _MHD_RED_ZONE_SIZE is 0 and this can never fire. */ + if (pool->pos > pool->size) + pool->pos = pool->size; pool->end = pool->size; _MHD_POISON_MEMORY (((uint8_t *) pool->memory) + new_size, \ pool->size - new_size);