In mimalloc v3 release builds, mi_free_size(p, size) routes every size <= MI_SMALL_SIZE_MAX through mi_free_small, which locates the page by aligning p down to MI_SMALL_PAGE_SIZE instead of doing a page-map lookup. That is only valid when the block lives in a small page. A small block with a large alignment is over-allocated (size + alignment - 1) and ends up in a medium page, so the computed page pointer is garbage and the process crashes.
mi_free_size_aligned(p, size, alignment) receives the alignment but discards it and defers to mi_free_size, so calling it with exactly the documented inputs ("the size of the object as allocated", "the requested alignment at the allocation") is enough to trigger the crash.
Reproducer
#include <mimalloc.h>
#include <stdio.h>
int main(void) {
const size_t size = 8;
const size_t alignment = 16 * 1024;
void* ptrs[200];
for (int i = 0; i < 200; i++) {
ptrs[i] = mi_malloc_aligned(size, alignment);
}
for (int i = 0; i < 200; i++) {
mi_free_size_aligned(ptrs[i], size, alignment); // SIGSEGV
}
printf("ok\n");
return 0;
}
$ cc -O2 -DNDEBUG -DMI_DEBUG=0 -Iinclude src/static.c repro.c -o repro && ./repro
Segmentation fault (exit 139)
A single allocation does not always crash: the first block happens to sit in the first 64 KiB chunk of the medium page, where the aligned-down address coincides with the page header. With a handful of blocks the crash is deterministic.
The debug build detects the exact condition and recovers:
$ cc -O0 -DMI_DEBUG=3 -DMI_SHOW_ERRORS=1 -Iinclude src/static.c repro.c -o repro_dbg && ./repro_dbg
mimalloc: error: thread 0x...: pointer 0x020000100000 is freed with mi_free_size but the given size 8 is less than the allocated block size 20480
(maybe a `new[]` was matched with `delete` instead of `delete[]`?)
...
ok
Expected
mi_free_size_aligned (and mi_free_size) should be safe for any pointer returned by the matching allocation call with the same size and alignment.
Environment
- mimalloc v3.5.2 (same code present on current
dev3)
- macOS 26 (Darwin 25.6.0), arm64, Apple clang 21
- Default build options,
MI_SECURE off, MI_GUARDED off (both of those disable MI_PAGE_META_SMALL_IS_ALIGNED and avoid the crash). v2 is unaffected because its mi_free_size is a plain mi_free.
Found while evaluating mi_free_size_aligned for GlobalAlloc::dealloc in a Rust wrapper, where the allocator must accept any power-of-two alignment.
In mimalloc v3 release builds,
mi_free_size(p, size)routes everysize <= MI_SMALL_SIZE_MAXthroughmi_free_small, which locates the page by aligningpdown toMI_SMALL_PAGE_SIZEinstead of doing a page-map lookup. That is only valid when the block lives in a small page. A small block with a large alignment is over-allocated (size + alignment - 1) and ends up in a medium page, so the computed page pointer is garbage and the process crashes.mi_free_size_aligned(p, size, alignment)receives the alignment but discards it and defers tomi_free_size, so calling it with exactly the documented inputs ("the size of the object as allocated", "the requested alignment at the allocation") is enough to trigger the crash.Reproducer
A single allocation does not always crash: the first block happens to sit in the first 64 KiB chunk of the medium page, where the aligned-down address coincides with the page header. With a handful of blocks the crash is deterministic.
The debug build detects the exact condition and recovers:
Expected
mi_free_size_aligned(andmi_free_size) should be safe for any pointer returned by the matching allocation call with the same size and alignment.Environment
dev3)MI_SECUREoff,MI_GUARDEDoff (both of those disableMI_PAGE_META_SMALL_IS_ALIGNEDand avoid the crash). v2 is unaffected because itsmi_free_sizeis a plainmi_free.Found while evaluating
mi_free_size_alignedforGlobalAlloc::deallocin a Rust wrapper, where the allocator must accept any power-of-two alignment.