1//===-- sanitizer_malloc_mac.inc --------------------------------*- C++ -*-===// 2// 3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4// See https://llvm.org/LICENSE.txt for license information. 5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6// 7//===----------------------------------------------------------------------===// 8// 9// This file contains Mac-specific malloc interceptors and a custom zone 10// implementation, which together replace the system allocator. 11// 12//===----------------------------------------------------------------------===// 13 14#include "sanitizer_common/sanitizer_platform.h" 15#if !SANITIZER_APPLE 16#error "This file should only be compiled on Darwin." 17#endif 18 19#include <AvailabilityMacros.h> 20#include <CoreFoundation/CFBase.h> 21#include <dlfcn.h> 22#include <malloc/malloc.h> 23#include <sys/mman.h> 24 25#include "interception/interception.h" 26#include "sanitizer_common/sanitizer_allocator_dlsym.h" 27#include "sanitizer_common/sanitizer_mac.h" 28 29// Similar code is used in Google Perftools, 30// https://github.com/gperftools/gperftools. 31 32namespace __sanitizer { 33 34extern malloc_zone_t sanitizer_zone; 35 36struct sanitizer_malloc_introspection_t : public malloc_introspection_t { 37 // IMPORTANT: Do not change the order, alignment, or types of these fields to 38 // maintain binary compatibility. You should only add fields to this struct. 39 40 // Used to track changes to the allocator that will affect 41 // zone enumeration. 42 u64 allocator_enumeration_version; 43 uptr allocator_ptr; 44 uptr allocator_size; 45}; 46 47u64 GetMallocZoneAllocatorEnumerationVersion() { 48 // This represents the current allocator ABI version. 49 // This field should be incremented every time the Allocator 50 // ABI changes in a way that breaks allocator enumeration. 51 return 0; 52} 53 54} // namespace __sanitizer 55 56INTERCEPTOR(malloc_zone_t *, malloc_create_zone, 57 vm_size_t start_size, unsigned zone_flags) { 58 COMMON_MALLOC_ENTER(); 59 uptr page_size = GetPageSizeCached(); 60 uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size); 61 COMMON_MALLOC_MEMALIGN(page_size, allocated_size); 62 malloc_zone_t *new_zone = (malloc_zone_t *)p; 63 internal_memcpy(new_zone, &sanitizer_zone, sizeof(sanitizer_zone)); 64 new_zone->zone_name = NULL; // The name will be changed anyway. 65 // Prevent the client app from overwriting the zone contents. 66 // Library functions that need to modify the zone will set PROT_WRITE on it. 67 // This matches the behavior of malloc_create_zone() on OSX 10.7 and higher. 68 mprotect(new_zone, allocated_size, PROT_READ); 69 // We're explicitly *NOT* registering the zone. 70 return new_zone; 71} 72 73INTERCEPTOR(void, malloc_destroy_zone, malloc_zone_t *zone) { 74 COMMON_MALLOC_ENTER(); 75 // We don't need to do anything here. We're not registering new zones, so we 76 // don't to unregister. Just un-mprotect and free() the zone. 77 uptr page_size = GetPageSizeCached(); 78 uptr allocated_size = RoundUpTo(sizeof(sanitizer_zone), page_size); 79 mprotect(zone, allocated_size, PROT_READ | PROT_WRITE); 80 if (zone->zone_name) { 81 COMMON_MALLOC_FREE((void *)zone->zone_name); 82 } 83 COMMON_MALLOC_FREE(zone); 84} 85 86INTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) { 87 COMMON_MALLOC_ENTER(); 88 return &sanitizer_zone; 89} 90 91INTERCEPTOR(malloc_zone_t *, malloc_zone_from_ptr, const void *ptr) { 92 COMMON_MALLOC_ENTER(); 93 size_t size = sanitizer_zone.size(&sanitizer_zone, ptr); 94 if (size) { // Claimed by sanitizer zone? 95 return &sanitizer_zone; 96 } 97 return REAL(malloc_zone_from_ptr)(ptr); 98} 99 100INTERCEPTOR(malloc_zone_t *, malloc_default_purgeable_zone, void) { 101 // FIXME: ASan should support purgeable allocations. 102 // https://github.com/google/sanitizers/issues/139 103 COMMON_MALLOC_ENTER(); 104 return &sanitizer_zone; 105} 106 107INTERCEPTOR(void, malloc_make_purgeable, void *ptr) { 108 // FIXME: ASan should support purgeable allocations. Ignoring them is fine 109 // for now. 110 COMMON_MALLOC_ENTER(); 111} 112 113INTERCEPTOR(int, malloc_make_nonpurgeable, void *ptr) { 114 // FIXME: ASan should support purgeable allocations. Ignoring them is fine 115 // for now. 116 COMMON_MALLOC_ENTER(); 117 // Must return 0 if the contents were not purged since the last call to 118 // malloc_make_purgeable(). 119 return 0; 120} 121 122INTERCEPTOR(void, malloc_set_zone_name, malloc_zone_t *zone, const char *name) { 123 COMMON_MALLOC_ENTER(); 124 InternalScopedString new_name; 125 if (name && zone->introspect == sanitizer_zone.introspect) { 126 new_name.append(COMMON_MALLOC_ZONE_NAME "-%s", name); 127 name = new_name.data(); 128 } 129 130 // Call the system malloc's implementation for both external and our zones, 131 // since that appropriately changes VM region protections on the zone. 132 REAL(malloc_set_zone_name)(zone, name); 133} 134 135INTERCEPTOR(void *, malloc, size_t size) { 136 COMMON_MALLOC_ENTER(); 137 COMMON_MALLOC_MALLOC(size); 138 return p; 139} 140 141INTERCEPTOR(void, free, void *ptr) { 142 COMMON_MALLOC_ENTER(); 143 if (!ptr) return; 144 COMMON_MALLOC_FREE(ptr); 145} 146 147INTERCEPTOR(void *, realloc, void *ptr, size_t size) { 148 COMMON_MALLOC_ENTER(); 149 COMMON_MALLOC_REALLOC(ptr, size); 150 return p; 151} 152 153INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) { 154 COMMON_MALLOC_ENTER(); 155 COMMON_MALLOC_CALLOC(nmemb, size); 156 return p; 157} 158 159INTERCEPTOR(void *, valloc, size_t size) { 160 COMMON_MALLOC_ENTER(); 161 COMMON_MALLOC_VALLOC(size); 162 return p; 163} 164 165INTERCEPTOR(size_t, malloc_good_size, size_t size) { 166 COMMON_MALLOC_ENTER(); 167 return sanitizer_zone.introspect->good_size(&sanitizer_zone, size); 168} 169 170INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { 171 COMMON_MALLOC_ENTER(); 172 CHECK(memptr); 173 COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size); 174 return res; 175} 176 177namespace { 178 179// TODO(glider): the __sanitizer_mz_* functions should be united with the Linux 180// wrappers, as they are basically copied from there. 181extern "C" 182SANITIZER_INTERFACE_ATTRIBUTE 183size_t __sanitizer_mz_size(malloc_zone_t* zone, const void* ptr) { 184 COMMON_MALLOC_SIZE(ptr); 185 return size; 186} 187 188extern "C" 189SANITIZER_INTERFACE_ATTRIBUTE 190void *__sanitizer_mz_malloc(malloc_zone_t *zone, uptr size) { 191 COMMON_MALLOC_ENTER(); 192 COMMON_MALLOC_MALLOC(size); 193 return p; 194} 195 196struct DlsymAlloc : public DlSymAllocator<DlsymAlloc> { 197 static bool UseImpl() { return !COMMON_MALLOC_SANITIZER_INITIALIZED; } 198}; 199 200extern "C" 201SANITIZER_INTERFACE_ATTRIBUTE 202void *__sanitizer_mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { 203 if (DlsymAlloc::Use()) 204 return DlsymAlloc::Callocate(nmemb, size); 205 COMMON_MALLOC_CALLOC(nmemb, size); 206 return p; 207} 208 209extern "C" 210SANITIZER_INTERFACE_ATTRIBUTE 211void *__sanitizer_mz_valloc(malloc_zone_t *zone, size_t size) { 212 COMMON_MALLOC_ENTER(); 213 COMMON_MALLOC_VALLOC(size); 214 return p; 215} 216 217// TODO(glider): the allocation callbacks need to be refactored. 218extern "C" 219SANITIZER_INTERFACE_ATTRIBUTE 220void __sanitizer_mz_free(malloc_zone_t *zone, void *ptr) { 221 if (!ptr) return; 222 if (DlsymAlloc::PointerIsMine(ptr)) 223 return DlsymAlloc::Free(ptr); 224 COMMON_MALLOC_FREE(ptr); 225} 226 227#define GET_ZONE_FOR_PTR(ptr) \ 228 malloc_zone_t *zone_ptr = WRAP(malloc_zone_from_ptr)(ptr); \ 229 const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name 230 231extern "C" 232SANITIZER_INTERFACE_ATTRIBUTE 233void *__sanitizer_mz_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) { 234 if (!ptr) { 235 COMMON_MALLOC_MALLOC(new_size); 236 return p; 237 } else { 238 COMMON_MALLOC_SIZE(ptr); 239 if (size) { 240 COMMON_MALLOC_REALLOC(ptr, new_size); 241 return p; 242 } else { 243 // We can't recover from reallocating an unknown address, because 244 // this would require reading at most |new_size| bytes from 245 // potentially unaccessible memory. 246 GET_ZONE_FOR_PTR(ptr); 247 COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name); 248 return nullptr; 249 } 250 } 251} 252 253extern "C" 254SANITIZER_INTERFACE_ATTRIBUTE 255void __sanitizer_mz_destroy(malloc_zone_t* zone) { 256 // A no-op -- we will not be destroyed! 257 Report("__sanitizer_mz_destroy() called -- ignoring\n"); 258} 259 260extern "C" 261SANITIZER_INTERFACE_ATTRIBUTE 262void *__sanitizer_mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { 263 COMMON_MALLOC_ENTER(); 264 COMMON_MALLOC_MEMALIGN(align, size); 265 return p; 266} 267 268// This public API exists purely for testing purposes. 269extern "C" 270SANITIZER_INTERFACE_ATTRIBUTE 271malloc_zone_t* __sanitizer_mz_default_zone() { 272 return &sanitizer_zone; 273} 274 275// This function is currently unused, and we build with -Werror. 276#if 0 277void __sanitizer_mz_free_definite_size( 278 malloc_zone_t* zone, void *ptr, size_t size) { 279 // TODO(glider): check that |size| is valid. 280 UNIMPLEMENTED(); 281} 282#endif 283 284#ifndef COMMON_MALLOC_HAS_ZONE_ENUMERATOR 285#error "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be defined" 286#endif 287static_assert((COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 0 || 288 (COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 1, 289 "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be 0 or 1"); 290 291#if COMMON_MALLOC_HAS_ZONE_ENUMERATOR 292// Forward declare and expect the implementation to provided by 293// includer. 294kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, 295 vm_address_t zone_address, memory_reader_t reader, 296 vm_range_recorder_t recorder); 297#else 298// Provide stub implementation that fails. 299kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, 300 vm_address_t zone_address, memory_reader_t reader, 301 vm_range_recorder_t recorder) { 302 // Not supported. 303 return KERN_FAILURE; 304} 305#endif 306 307#ifndef COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT 308#error "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be defined" 309#endif 310static_assert((COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 0 || 311 (COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 1, 312 "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be 0 or 1"); 313#if COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT 314// Forward declare and expect the implementation to provided by 315// includer. 316void mi_extra_init( 317 sanitizer_malloc_introspection_t *mi); 318#else 319void mi_extra_init( 320 sanitizer_malloc_introspection_t *mi) { 321 // Just zero initialize the fields. 322 mi->allocator_ptr = 0; 323 mi->allocator_size = 0; 324} 325#endif 326 327size_t mi_good_size(malloc_zone_t *zone, size_t size) { 328 // I think it's always safe to return size, but we maybe could do better. 329 return size; 330} 331 332boolean_t mi_check(malloc_zone_t *zone) { 333 UNIMPLEMENTED(); 334} 335 336void mi_print(malloc_zone_t *zone, boolean_t verbose) { 337 UNIMPLEMENTED(); 338} 339 340void mi_log(malloc_zone_t *zone, void *address) { 341 // I don't think we support anything like this 342} 343 344void mi_force_lock(malloc_zone_t *zone) { 345 COMMON_MALLOC_FORCE_LOCK(); 346} 347 348void mi_force_unlock(malloc_zone_t *zone) { 349 COMMON_MALLOC_FORCE_UNLOCK(); 350} 351 352void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { 353 COMMON_MALLOC_FILL_STATS(zone, stats); 354} 355 356boolean_t mi_zone_locked(malloc_zone_t *zone) { 357 // UNIMPLEMENTED(); 358 return false; 359} 360 361} // unnamed namespace 362 363namespace COMMON_MALLOC_NAMESPACE { 364 365void InitMallocZoneFields() { 366 static sanitizer_malloc_introspection_t sanitizer_zone_introspection; 367 // Ok to use internal_memset, these places are not performance-critical. 368 internal_memset(&sanitizer_zone_introspection, 0, 369 sizeof(sanitizer_zone_introspection)); 370 371 sanitizer_zone_introspection.enumerator = &mi_enumerator; 372 sanitizer_zone_introspection.good_size = &mi_good_size; 373 sanitizer_zone_introspection.check = &mi_check; 374 sanitizer_zone_introspection.print = &mi_print; 375 sanitizer_zone_introspection.log = &mi_log; 376 sanitizer_zone_introspection.force_lock = &mi_force_lock; 377 sanitizer_zone_introspection.force_unlock = &mi_force_unlock; 378 sanitizer_zone_introspection.statistics = &mi_statistics; 379 sanitizer_zone_introspection.zone_locked = &mi_zone_locked; 380 381 // Set current allocator enumeration version. 382 sanitizer_zone_introspection.allocator_enumeration_version = 383 GetMallocZoneAllocatorEnumerationVersion(); 384 385 // Perform any sanitizer specific initialization. 386 mi_extra_init(&sanitizer_zone_introspection); 387 388 internal_memset(&sanitizer_zone, 0, sizeof(malloc_zone_t)); 389 390 // Use version 6 for OSX >= 10.6. 391 sanitizer_zone.version = 6; 392 sanitizer_zone.zone_name = COMMON_MALLOC_ZONE_NAME; 393 sanitizer_zone.size = &__sanitizer_mz_size; 394 sanitizer_zone.malloc = &__sanitizer_mz_malloc; 395 sanitizer_zone.calloc = &__sanitizer_mz_calloc; 396 sanitizer_zone.valloc = &__sanitizer_mz_valloc; 397 sanitizer_zone.free = &__sanitizer_mz_free; 398 sanitizer_zone.realloc = &__sanitizer_mz_realloc; 399 sanitizer_zone.destroy = &__sanitizer_mz_destroy; 400 sanitizer_zone.batch_malloc = 0; 401 sanitizer_zone.batch_free = 0; 402 sanitizer_zone.free_definite_size = 0; 403 sanitizer_zone.memalign = &__sanitizer_mz_memalign; 404 sanitizer_zone.introspect = &sanitizer_zone_introspection; 405} 406 407void ReplaceSystemMalloc() { 408 InitMallocZoneFields(); 409 410 // Register the zone. 411 malloc_zone_register(&sanitizer_zone); 412} 413 414} // namespace COMMON_MALLOC_NAMESPACE 415