microsoft / microsoft/mimalloc
LLM audit
Nobody has claimed this yet.
- Dominant language
- C
- Stars
- 13.4k
- Forks
- 1.2k
- Avg merge
- 4d 45m
- Merged PRs (30d)
- 13
Description
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`
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing the listed source locations and comparing each reported behavior with the referenced declarations, callers, and documentation. Confirm the suspected issues individually across the relevant platforms; the work is done when each finding is either supported by a focused reproduction or dismissed as a false positive.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- operating-systems, performance
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100