|
| 1 | +/* ---------------------------------------------------------------------------- |
| 2 | +Copyright (c) Microsoft Research, Daan Leijen |
| 3 | +This is free software; you can redistribute it and/or modify it under the |
| 4 | +terms of the MIT license. |
| 5 | +-----------------------------------------------------------------------------*/ |
| 6 | + |
| 7 | +/* `free(NULL)` must work before mimalloc has initialized (upstream issue #1341). |
| 8 | +
|
| 9 | + glibc 2.44's `__newlocale` calls `free(NULL)` from the dynamic loader, before any constructor |
| 10 | + of the executable has run. With `malloc` overridden that lands in `mi_free`, which looks `p` |
| 11 | + up in the page map without a NULL check: `_mi_unchecked_ptr_page` reads `submaps[0][0]`. The |
| 12 | + initial (static) page map has to carry a real all-NULL submap at index 0; with a NULL submap |
| 13 | + the lookup faults at address 0 and the process dies before `main()`. |
| 14 | +
|
| 15 | + On ELF the call is made from `.preinit_array`, which the loader runs before every |
| 16 | + `.init_array` entry of the executable and its libraries -- the same point in process startup |
| 17 | + as the glibc call. Elsewhere a plain constructor is the closest available approximation (its |
| 18 | + order relative to mimalloc's own constructor is not guaranteed). */ |
| 19 | + |
| 20 | +#include <stdio.h> |
| 21 | +#include <stdlib.h> |
| 22 | +#include <mimalloc.h> |
| 23 | + |
| 24 | +static int calls_before_init = 0; |
| 25 | + |
| 26 | +static void free_null_before_init(void) { |
| 27 | + free(NULL); // reaches mi_free only when malloc is overridden |
| 28 | + mi_free(NULL); // always reaches the page-map lookup |
| 29 | + calls_before_init++; |
| 30 | +} |
| 31 | + |
| 32 | +#if defined(__ELF__) |
| 33 | +__attribute__((section(".preinit_array"), used)) |
| 34 | +static void (*mi_test_preinit)(void) = &free_null_before_init; |
| 35 | +#elif defined(__GNUC__) || defined(__clang__) |
| 36 | +__attribute__((constructor)) |
| 37 | +static void free_null_before_init_ctor(void) { free_null_before_init(); } |
| 38 | +#endif |
| 39 | + |
| 40 | +int main(void) { |
| 41 | + if (calls_before_init != 1) { |
| 42 | + printf("test-free-before-init: FAILED, the pre-init hook ran %d times\n", calls_before_init); |
| 43 | + return 1; |
| 44 | + } |
| 45 | + // the real page map replaced the static one: allocation and free still work |
| 46 | + void* p = mi_malloc(64); |
| 47 | + if (p == NULL) { |
| 48 | + printf("test-free-before-init: FAILED, mi_malloc returned NULL after the pre-init free\n"); |
| 49 | + return 1; |
| 50 | + } |
| 51 | + mi_free(p); |
| 52 | + free(NULL); |
| 53 | + mi_free(NULL); |
| 54 | + printf("test-free-before-init: ok\n"); |
| 55 | + return 0; |
| 56 | +} |
0 commit comments