Skip to content

LLM audit #1271

@Zoxc

Description

@Zoxc

I want to make use of mimalloc in rustc, so I did an LLM audit on dev3 branch, commit e6997f583d17a3d3e2a9785eebc1e35b85503012. I've found LLMs quite good at spotting bugs, though with some false positives. Most false positives they surface seems worthy of a comment to guide both LLMs and humans in the right direction however. I've only looked at a couple of these so they are not all confirmed bugs.

List of bugs

High

Issue 5: Non-Windows _aligned_malloc wrapper swaps size and alignment [Looks like a false positive, but comment worthy]

  • Severity: High
  • Location: src/alloc-override.c:360-361
  • Details: The compatibility wrapper is declared as _aligned_malloc(size_t alignment, size_t size) and forwards mi_aligned_alloc(alignment, size), so callers using _aligned_malloc(size, alignment) can receive undersized allocations.
  • Evidence: src/alloc-override.c:186-187,360-361; include/mimalloc-override.h:59

Medium

Issue 9: mi_manage_memory swaps is_pinned and is_zero

  • Severity: Medium
  • Location: src/arena.c:1656-1664
  • Details: The public declaration uses (..., is_pinned, is_zero, ...), but the definition uses (..., is_zero, is_pinned, ...), reversing runtime semantics for pinned and zero-initialized managed memory.
  • Evidence: include/mimalloc.h:401-403; src/arena.c:1656-1664

Issue 24: Deferred free callback/arg publication race

  • Severity: Medium
  • Location: src/page.c:883-897
  • Details: deferred_free is published before deferred_arg and read without matching acquire semantics, so concurrent registration can invoke the new callback with stale context.
  • Evidence: src/page.c:883-897; src/page.c:987-991

Issue 27: Fallback constructor misbinds main allocator to dlopen() thread

  • Severity: Medium
  • Location: src/prim/prim.c:30-45
  • Details: The fallback constructor runs _mi_auto_process_init() on whichever thread loads the library, so a worker thread can become the recorded "main" allocator thread and later tear down static main-thread state when it exits.
  • Evidence: src/prim/prim.c:30-45; src/init.c:965-977

Issue 40: ETW provider is never unregistered on unload

  • Severity: Medium
  • Location: src/prim/windows/etw.h:733-741
  • Details: ETW registration happens during process init, but the Windows detach path never calls EventUnregistermicrosoft_windows_mimalloc(), and the generated ETW header explicitly warns that unload without unregister can crash.
  • Evidence: include/mimalloc/track.h:78-87; src/init.c:1112-1158; src/prim/windows/etw.h:733-741

Low

Issue 2: Broken once primitive allows partial initialization escape

  • Severity: Low
  • Location: include/mimalloc/atomic.h:343-350
  • Details: mi_atomic_once only performs a 0->1 CAS and does not wait for initialization completion, so racing threads can proceed while process/TLS initialization is still incomplete.
  • Evidence: include/mimalloc/atomic.h:343-350; src/init.c:696-704; include/mimalloc/prim.h:470-486

Issue 3: BMI1 mi_bsr fast path returns lzcnt, not the MSB index

  • Severity: Low
  • Location: include/mimalloc/bits.h:275-285
  • Details: The BMI1 branch stores the raw lzcnt result into *idx instead of converting it to MI_SIZE_BITS - 1 - lzcnt(x), so callers get mirrored bit indexes.
  • Evidence: include/mimalloc/bits.h:275-285; src/bitmap.c:952-971

Issue 4: Invalid pthread TLS key use in macOS PowerPC fallback

  • Severity: Low
  • Location: include/mimalloc/prim.h:185-187
  • Details: The Apple PowerPC fallback passes fixed TLS slot numbers to pthread_getspecific and pthread_setspecific even though no pthread_key_create key exists for those values.
  • Evidence: include/mimalloc/prim.h:185-187,225-227

Issue 6: realloc_aligned_at drops the offset requirement for small alignments

  • Severity: Low
  • Location: src/alloc-aligned.c:329-340
  • Details: mi_theap_realloc_zero_aligned_at falls back to generic realloc when alignment <= sizeof(uintptr_t) and ignores offset, so the returned pointer can violate the documented ((uintptr_t)p + offset) % alignment == 0 contract.
  • Evidence: src/alloc-aligned.c:329-340; doc/mimalloc-doc.h:320-334

Issue 7: mi_reallocarr violates NetBSD zero-count semantics

  • Severity: Low
  • Location: src/alloc-posix.c:113-126
  • Details: Zero-sized reallocarr requests are forwarded to mi_realloc(p, 0), but mimalloc intentionally returns a zero-sized allocation instead of freeing and nulling the pointer as NetBSD reallocarr requires.
  • Evidence: src/alloc-posix.c:113-126; src/alloc.c:361-405

Issue 8: mi_theap_realpath leaks a foreign realpath buffer

  • Severity: Low
  • Location: src/alloc.c:552-562
  • Details: The POSIX path duplicates realpath(fname, NULL) into mimalloc memory and then calls mi_cfree on the original, but mi_cfree ignores non-mimalloc allocations.
  • Evidence: src/alloc.c:552-562; src/alloc-posix.c:49-53

Issue 12: _mi_bitmap_forall_setc_rangesn can ignore an early stop request

  • Severity: Low
  • Location: src/bitmap.c:1497-1518
  • Details: If visit(...) returns false while skipped == 0, the function does not return immediately and can continue clearing and visiting later ranges.
  • Evidence: src/bitmap.c:1497-1518; src/bitmap.h:214-226

Issue 13: Wrong debug assertion bound in tail chunk path

  • Severity: Low
  • Location: src/bitmap.c:1050-1076
  • Details: The assertion checks chunk_idx < MI_BCHUNK_FIELDS even though chunk_idx indexes the chunk array, so valid large bitmaps can trip it in debug builds.
  • Evidence: src/bitmap.c:1050-1076; src/bitmap.h:70-87

Issue 15: Subprocess teardown can self-deadlock

  • Severity: Low
  • Location: src/init.c:545-556
  • Details: mi_subprocs_unsafe_destroy_all() holds subprocs_lock and calls mi_subproc_unsafe_destroy(), which immediately re-locks the same non-recursive mutex.
  • Evidence: src/init.c:498-505,545-556

Issue 17: Partial huge-page allocation records the requested size instead of the actual size

  • Severity: Low
  • Location: src/os.c:733-795
  • Details: On partial success, *psize is reduced but memid.mem.os.size stays at the original request, so MI_MEM_OS_HUGE free/accounting paths later use the wrong span.
  • Evidence: src/os.c:733-795; src/os.c:249-272

Issue 18: Output callback registration races with output emission

  • Severity: Low
  • Location: src/options.c:403-416
  • Details: The output callback and callback argument are not atomically published as a pair, so concurrent readers can observe a new callback with an old argument.
  • Evidence: src/options.c:403-416; src/options.c:470-476

Issue 19: Error callback registration races with error delivery

  • Severity: Low
  • Location: src/options.c:599-613
  • Details: mi_register_error() and _mi_error_message() access the handler pointer without synchronization, and the handler/arg pair can be observed in a mixed state.
  • Evidence: src/options.c:574-575,599-613

Issue 20: Boolean environment parser accepts arbitrary substrings

  • Severity: Low
  • Location: src/options.c:645-655
  • Details: The parser uses substring search against 1;TRUE;YES;ON and 0;FALSE;NO;OFF, so malformed values like RUE, AL, or ; are accepted as valid booleans.
  • Evidence: src/options.c:645-655

Issue 23: mi_good_size can overflow and return a smaller-than-requested value

  • Severity: Low
  • Location: src/page-queue.c:114-120
  • Details: The function adds padding and aligns without overflow checks, so near-SIZE_MAX inputs can wrap and violate the documented n >= size contract.
  • Evidence: src/page-queue.c:114-120; doc/mimalloc-doc.h:272-281

Issue 25: malloc_zone_from_ptr always returns mimalloc's zone

  • Severity: Low
  • Location: src/prim/osx/alloc-override-zone.c:330-333
  • Details: zone_from_ptr() ignores its pointer argument and always reports the default mimalloc zone, so callers can misroute foreign pointers into mimalloc's zone free/realloc path.
  • Evidence: src/prim/osx/alloc-override-zone.c:330-333; src/prim/osx/alloc-override-zone.c:380-391

Issue 26: Lazy zone registration is racy

  • Severity: Low
  • Location: src/prim/osx/alloc-override-zone.c:245-265
  • Details: mi_get_default_zone() uses an unsynchronized static init flag around malloc_zone_register, allowing duplicate registration races during concurrent startup.
  • Evidence: src/prim/osx/alloc-override-zone.c:245-265

Issue 28: Linux physical-memory detection ignores sysinfo.mem_unit

  • Severity: Low
  • Location: src/prim/unix/prim.c:184-190
  • Details: The Linux path divides info.totalram directly by MI_KiB, but totalram is expressed in mem_unit units rather than bytes.
  • Evidence: src/prim/unix/prim.c:184-190

Issue 31: Windows large-page one-time initialization is racy

  • Severity: Low
  • Location: src/prim/windows/prim.c:93-128
  • Details: win_enable_large_os_pages() uses a plain static large_initialized guard, so concurrent callers can race and observe incomplete setup.
  • Evidence: src/prim/windows/prim.c:93-128; src/prim/windows/prim.c:412-416

Issue 32: Huge-page availability cache is racy

  • Severity: Low
  • Location: src/prim/windows/prim.c:420-438
  • Details: _mi_prim_alloc_huge_os_pagesx() uses a shared plain static mi_huge_pages_available flag without synchronization, so concurrent callers can race and disable attempts nondeterministically.
  • Evidence: src/prim/windows/prim.c:420-438

Issue 33: NULL stderr handle is misclassified as usable

  • Severity: Low
  • Location: src/prim/windows/prim.c:593-616
  • Details: _mi_prim_out_stderr() only treats INVALID_HANDLE_VALUE as invalid, so a NULL handle reaches WriteFile(NULL, ...) instead of the fallback path and diagnostics are lost.
  • Evidence: src/prim/windows/prim.c:593-616

Issue 34: Weak-seed fallback uses undefined uint32_t* stores

  • Severity: Low
  • Location: src/random.c:172-190
  • Details: The fallback fills uint8_t key[32] through ((uint32_t*)key)[i], which assumes alignment and violates C's type rules for the object.
  • Evidence: src/random.c:172-190

Issue 37: TLS key version is truncated to half a word

  • Severity: Low
  • Location: src/threadlocal.c:39-60
  • Details: Key encoding stores the version in only the upper half of the word while the global version counter advances as a full size_t, so very old stale keys can eventually alias reused slots.
  • Evidence: src/threadlocal.c:39-60; src/threadlocal.c:120-126

Issue 38: Function pointer is passed through void*

  • Severity: Low
  • Location: src/theap.c:675-688
  • Details: mi_theap_visit_areas() casts a function pointer through void* and back, which is non-portable undefined behavior on architectures with distinct code/data pointer representations.
  • Evidence: src/theap.c:675-688

None / Informational

Issue 10: Managed-arena allocation bypasses commit_fun

  • Severity: None
  • Location: src/arena.c:236-253
  • Details: mi_manage_memory() stores the user commit callback, but mi_arena_try_alloc_at() commits with _mi_os_commit_ex() instead of the mi_arena_commit() helper, so managed-arena commit callbacks can be skipped.
  • Evidence: src/arena.c:236-253; src/arena.c:1656-1664

Issue 14: arena_pages_lock is never destroyed on heap teardown

  • Severity: None
  • Location: src/heap.c:167-194
  • Details: mi_heap_new_in_arena() initializes arena_pages_lock, but mi_heap_free() destroys only the other heap locks before freeing the heap object.
  • Evidence: src/heap.c:119-121,167-194,208-212

Issue 30: ETW enable-state updates race with alloc/free checks

  • Severity: None
  • Location: src/prim/windows/etw.h:381-390
  • Details: Generated ETW code updates provider enable bits with ordinary stores and RMW operations while hot-path event checks read them concurrently; this breaks tracing-state synchronization but does not directly affect allocator integrity.
  • Evidence: src/prim/windows/etw.h:381-390,396-402,649-655

Issue 35: mi_subproc_stats_get clears the output header

  • Severity: None
  • Location: src/stats.c:606-612
  • Details: The function validates stats->size and stats->version, zeroes the whole buffer, and never restores those header fields before returning success.
  • Evidence: src/stats.c:606-612; src/stats.c:119-133

Issue 36: Subprocess JSON export drops per-heap statistics

  • Severity: None
  • Location: src/stats.c:790-795
  • Details: mi_subproc_stats_get_json() aggregates into a local stats buffer but serializes &subproc->stats instead, omitting live heap data.
  • Evidence: src/stats.c:723-779; src/stats.c:790-795

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions