Skip to content

MI_SECURE=2: mi_find_free_page calls mi_page_extend_free while page->free != NULL (assert) #1342

Description

@psiha

Summary

Building with -DMI_SECURE=2 and assertions enabled (MI_DEBUG / no NDEBUG) aborts in mi_page_extend_free with:

mimalloc: assertion failed: .../src/page.c: mi_page_extend_free
  assertion: "page->free == NULL"

This looks like an internal inconsistency between two #if MI_SECURE gates, not application heap corruption. Levels 0/1 never take the offending call; levels ≥3 compile out the assert. Observed on Windows and Linux with a recent v3.3.x-theap pin; the same structural clash is still present on current dev (classic mi_heap_t API).

Conflicting sites (theap / v3.3-style page.c)

After a successful mi_page_free_quick_collect (page already has free blocks), secure≥2 randomly extends:

// mi_find_free_page fast path
if (page != NULL && mi_page_free_quick_collect(page)) {
#if (MI_SECURE>=2) // extend half the time to increase randomness
  if (page->capacity < page->reserved && ((_mi_theap_random_next(theap) & 1) == 1)) {
    (void)mi_page_extend_free(theap, page);
  }
#endif
  return page;
}

But mi_page_extend_free still requires an empty free list when secure<3:

static bool mi_page_extend_free(...) {
#if (MI_SECURE<3)
  mi_assert(page->free == NULL);
  mi_assert(page->local_free == NULL);
  if (page->free != NULL) return true;
#endif
  ...
}

So exactly MI_SECURE == 2: random-extend-on-hot-page is enabled, while page->free == NULL is still asserted. With asserts off, the early return true would skip the extend (silent no-op of the randomness feature); with asserts on, it aborts.

Upstream dev (classic heap)

Same pattern in mi_find_free_page (MI_SECURE>=2 random mi_page_extend_free) vs assert gated by MI_SECURE<=2 in mi_page_extend_free.

Suggested fixes (either)

  1. Do not call mi_page_extend_free from the fast path when page->free != NULL / when the page is already immediately available; or
  2. Allow prepend at secure=2 and align the assert gate with the call site (e.g. only assert empty free list when secure<2), matching how secure≥3 already allows a non-empty free list during extend.

Reproduction notes

Any assert-enabled build with -DMI_SECURE=2 that allocates enough to hit the fast path coin-flip should trip this (allocation-heavy C++ workload on Windows clang-cl and Linux clang-22). Happy to try a minimal standalone repro if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions