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:
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);