Post

Replies

Boosts

Views

Activity

Behavior change for malloc zones in macOS Ventura
In our codebase we have an option in our Debug builds to replace the malloc implementation with a wrapper function so we can write unit tests to detect allocations in code we expect to be allocation free. We were doing this using malloc_default_zone() to get the malloc zone and then vm_protect to make the zone read/write to change malloc and free to wrapper functions (then restore the permissions on the zone with vm_protect. We noticed this was no longer working on macOS Ventura. When trying to research this I couldn't find anything related in the change logs but did find this StackOverflow post suggesting to use malloc_get_all_zones() instead of malloc_default_zone(). We tried this and still were not able to override the malloc behavior. In the example program below I would expect to see some MY MALLOC! message printed when malloc is called: #include <array> #include <cstdint> #include <iostream> #include <mach/mach.h> #include <malloc/malloc.h> using malloc_ptr = void *(*)(malloc_zone_t *zone, size_t size); std::array<malloc_ptr, 3> original_mallocs; template <size_t N> void *my_malloc(malloc_zone_t *zone, size_t size) { std::cout << "MY MALLOC! " << N << '\n'; return original_mallocs[N](zone, size); } template <size_t N> void hook_malloc(auto zone) { original_mallocs[N] = zone->malloc; vm_protect(mach_task_self(), (uintptr_t)zone, sizeof(malloc_zone_t), 0, VM_PROT_READ | VM_PROT_WRITE); zone->malloc = &my_malloc<N>; } void setup_malloc_hooks() { malloc_zone_t **zones = nullptr; unsigned int num_zones = 0; if (KERN_SUCCESS != malloc_get_all_zones(0, NULL, (vm_address_t **)&zones, &num_zones)) { // Reset the value in case the failure happened after it was num_zones = 0; } if (num_zones) { for (auto i = 0; i < num_zones; ++i) { switch (i) { case 0: std::cout << "hooking zone 0\n"; hook_malloc<0>(zones[0]); break; case 1: std::cout << "hooking zone 1\n"; hook_malloc<1>(zones[1]); break; case 2: std::cout << "hooking zone 2\n"; hook_malloc<2>(zones[2]); break; default: std::cout << "NOT hooking zone " << i << '\n'; break; } } } else { std::cout << "using default zone\n"; hook_malloc<0u>(malloc_default_zone()); } std::cout << '\n'; } void print_mallocs() { malloc_zone_t **zones = nullptr; unsigned int num_zones = 0; if (KERN_SUCCESS != malloc_get_all_zones(0, NULL, (vm_address_t **)&zones, &num_zones)) { // Reset the value in case the failure happened after it was num_zones = 0; } if (num_zones) { for (auto i = 0; i < num_zones; ++i) { std::cout << "zone: " << reinterpret_cast<uintptr_t>(zones[i]->malloc) << '\n'; } } else { auto zone = malloc_default_zone(); std::cout << "default zone: " << reinterpret_cast<uintptr_t>(zone->malloc) << '\n'; } std::cout << '\n'; } int main(int argc, const char *argv[]) { print_mallocs(); setup_malloc_hooks(); print_mallocs(); void *v = malloc(123); uintptr_t my_v = reinterpret_cast<uintptr_t>(v); std::cout << "my_v is " << my_v << "\n"; return 0; } Does anyone have any insight into what might have changed or how we can work around it?
2
1
863
Mar ’24