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.

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions