fix red-zone support in memorypool

This commit is contained in:
Christian Grothoff
2026-07-29 13:19:49 +02:00
parent 75549625f0
commit 669daf7a46
+42 -7
View File
@@ -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);