For allocation, the block size calculation is snapped to block bin sizes, but for reallocation it is not. So the tail_size calculation will be wrong if the block size is not one of the bin sizes.
To illustrate the problem: assume an allocation size of 32 bytes. heap_get_block_size will return 40 bytes. heap_allocate_block_lfh will round it up to 48 bytes. The tail_size is thus, 48 - 8 - 32 = 8 bytes. Later, HeapReAlloc is called to shrink it to 30 bytes. heap_get_block_size returns 40 bytes, heap_resize_block_lfh will not return STATUS_NO_MEMORY, because ROUND_SIZE(30) == 32, and 30 < 32. It will then calculate the tail_size based on the new block_size, which is 40 bytes. So the new tail_size becomes: 40 - 8 - 30 = 2 bytes. But block->block_size is still 48 bytes! So what it actually did is **growing** the block to 48 - 8 - 2 = 38 bytes from 32 bytes.
This commit fixes it by also rounding up the block_size to bin sizes in heap_resize_block_lfh.
* * *
This was discovered serendipitously by ASan, because the heap shadowing code I added for ASan didn't expect `heap_allocate_block_lfh` to enlarged the block, this mismatch triggered an ASan report.
From: Yuxuan Shui yshui@codeweavers.com
For allocation, the block size calculation is snapped to block bin sizes, but for reallocation it is not. So the tail_size calculation will be wrong if the block size is not one of the bin sizes.
To illustrate the problem: assume an allocation size of 32 bytes. heap_get_block_size will return 40 bytes. heap_allocate_block_lfh will round it up to 48 bytes. The tail_size is thus, 48 - 8 - 32 = 8 bytes. Later, HeapReAlloc is called to shrink it to 30 bytes. heap_get_block_size returns 40 bytes, heap_resize_block_lfh will not return STATUS_NO_MEMORY, because ROUND_SIZE(30) == 32, and 30 < 32. It will then calculate the tail_size based on the new block_size, which is 40 bytes. So the new tail_size becomes: 40 - 8 - 30 = 2 bytes. But block->block_size is still 48 bytes! So what it actually did is **growing** the block to 48 - 8 - 2 = 38 bytes from 32 bytes.
This commit fixes it by also rounding up the block_size to bin sizes in heap_resize_block_lfh. --- dlls/ntdll/heap.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/dlls/ntdll/heap.c b/dlls/ntdll/heap.c index f999fcbadf2..d97119b7731 100644 --- a/dlls/ntdll/heap.c +++ b/dlls/ntdll/heap.c @@ -2177,6 +2177,7 @@ static NTSTATUS heap_resize_block_lfh( struct block *block, ULONG flags, SIZE_T if (ROUND_SIZE( *old_size, BLOCK_ALIGN - 1) != ROUND_SIZE( size, BLOCK_ALIGN - 1)) return STATUS_NO_MEMORY; if (size >= *old_size) return STATUS_NO_MEMORY;
+ block_size = BLOCK_BIN_SIZE( BLOCK_SIZE_BIN( block_size ) ); block_set_flags( block, BLOCK_FLAG_USER_MASK & ~BLOCK_FLAG_USER_INFO, BLOCK_USER_FLAGS( flags ) ); block->tail_size = block_size - sizeof(*block) - size; initialize_block( block, *old_size, size, flags );
This merge request was approved by Rémi Bernon.