| 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 | |
| 32 | namespace __sanitizer { |
| 33 | |
| 34 | extern malloc_zone_t sanitizer_zone; |
| 35 | |
| 36 | struct 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 | |
| 47 | u64 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 | |
| 56 | INTERCEPTOR(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 | |
| 73 | INTERCEPTOR(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 | |
| 86 | INTERCEPTOR(malloc_zone_t *, malloc_default_zone, void) { |
| 87 | COMMON_MALLOC_ENTER(); |
| 88 | return &sanitizer_zone; |
| 89 | } |
| 90 | |
| 91 | INTERCEPTOR(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 | |
| 100 | INTERCEPTOR(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 | |
| 107 | INTERCEPTOR(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 | |
| 113 | INTERCEPTOR(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 | |
| 122 | INTERCEPTOR(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.AppendF(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 | |
| 135 | INTERCEPTOR(void *, malloc, size_t size) { |
| 136 | COMMON_MALLOC_ENTER(); |
| 137 | COMMON_MALLOC_MALLOC(size); |
| 138 | return p; |
| 139 | } |
| 140 | |
| 141 | INTERCEPTOR(void, free, void *ptr) { |
| 142 | COMMON_MALLOC_ENTER(); |
| 143 | if (!ptr) return; |
| 144 | COMMON_MALLOC_FREE(ptr); |
| 145 | } |
| 146 | |
| 147 | #if SANITIZER_INTERCEPT_FREE_SIZED && defined(COMMON_MALLOC_FREE_SIZED) |
| 148 | INTERCEPTOR(void, free_sized, void *ptr, size_t size) { |
| 149 | COMMON_MALLOC_ENTER(); |
| 150 | COMMON_MALLOC_FREE_SIZED(ptr, size); |
| 151 | } |
| 152 | #endif |
| 153 | |
| 154 | #if SANITIZER_INTERCEPT_FREE_ALIGNED_SIZED && \ |
| 155 | defined(COMMON_MALLOC_FREE_ALIGNED_SIZED) |
| 156 | INTERCEPTOR(void, free_aligned_sized, void *ptr, size_t alignment, |
| 157 | size_t size) { |
| 158 | COMMON_MALLOC_ENTER(); |
| 159 | COMMON_MALLOC_FREE_ALIGNED_SIZED(ptr, alignment, size); |
| 160 | } |
| 161 | #endif |
| 162 | |
| 163 | INTERCEPTOR(void *, realloc, void *ptr, size_t size) { |
| 164 | COMMON_MALLOC_ENTER(); |
| 165 | COMMON_MALLOC_REALLOC(ptr, size); |
| 166 | return p; |
| 167 | } |
| 168 | |
| 169 | INTERCEPTOR(void *, calloc, size_t nmemb, size_t size) { |
| 170 | COMMON_MALLOC_ENTER(); |
| 171 | COMMON_MALLOC_CALLOC(nmemb, size); |
| 172 | return p; |
| 173 | } |
| 174 | |
| 175 | INTERCEPTOR(void *, valloc, size_t size) { |
| 176 | COMMON_MALLOC_ENTER(); |
| 177 | COMMON_MALLOC_VALLOC(size); |
| 178 | return p; |
| 179 | } |
| 180 | |
| 181 | INTERCEPTOR(size_t, malloc_good_size, size_t size) { |
| 182 | COMMON_MALLOC_ENTER(); |
| 183 | return sanitizer_zone.introspect->good_size(&sanitizer_zone, size); |
| 184 | } |
| 185 | |
| 186 | INTERCEPTOR(int, posix_memalign, void **memptr, size_t alignment, size_t size) { |
| 187 | COMMON_MALLOC_ENTER(); |
| 188 | CHECK(memptr); |
| 189 | COMMON_MALLOC_POSIX_MEMALIGN(memptr, alignment, size); |
| 190 | return res; |
| 191 | } |
| 192 | |
| 193 | namespace { |
| 194 | |
| 195 | // TODO(glider): the __sanitizer_mz_* functions should be united with the Linux |
| 196 | // wrappers, as they are basically copied from there. |
| 197 | extern "C" |
| 198 | SANITIZER_INTERFACE_ATTRIBUTE |
| 199 | size_t __sanitizer_mz_size(malloc_zone_t* zone, const void* ptr) { |
| 200 | COMMON_MALLOC_SIZE(ptr); |
| 201 | return size; |
| 202 | } |
| 203 | |
| 204 | extern "C" |
| 205 | SANITIZER_INTERFACE_ATTRIBUTE |
| 206 | void *__sanitizer_mz_malloc(malloc_zone_t *zone, uptr size) { |
| 207 | COMMON_MALLOC_ENTER(); |
| 208 | COMMON_MALLOC_MALLOC(size); |
| 209 | return p; |
| 210 | } |
| 211 | |
| 212 | struct DlsymAlloc : public DlSymAllocator<DlsymAlloc> { |
| 213 | static bool UseImpl() { return !COMMON_MALLOC_SANITIZER_INITIALIZED; } |
| 214 | }; |
| 215 | |
| 216 | extern "C" |
| 217 | SANITIZER_INTERFACE_ATTRIBUTE |
| 218 | void *__sanitizer_mz_calloc(malloc_zone_t *zone, size_t nmemb, size_t size) { |
| 219 | if (DlsymAlloc::Use()) |
| 220 | return DlsymAlloc::Callocate(nmemb, size); |
| 221 | COMMON_MALLOC_CALLOC(nmemb, size); |
| 222 | return p; |
| 223 | } |
| 224 | |
| 225 | extern "C" |
| 226 | SANITIZER_INTERFACE_ATTRIBUTE |
| 227 | void *__sanitizer_mz_valloc(malloc_zone_t *zone, size_t size) { |
| 228 | COMMON_MALLOC_ENTER(); |
| 229 | COMMON_MALLOC_VALLOC(size); |
| 230 | return p; |
| 231 | } |
| 232 | |
| 233 | // TODO(glider): the allocation callbacks need to be refactored. |
| 234 | extern "C" |
| 235 | SANITIZER_INTERFACE_ATTRIBUTE |
| 236 | void __sanitizer_mz_free(malloc_zone_t *zone, void *ptr) { |
| 237 | if (!ptr) return; |
| 238 | if (DlsymAlloc::PointerIsMine(ptr)) |
| 239 | return DlsymAlloc::Free(ptr); |
| 240 | COMMON_MALLOC_FREE(ptr); |
| 241 | } |
| 242 | |
| 243 | #define GET_ZONE_FOR_PTR(ptr) \ |
| 244 | malloc_zone_t *zone_ptr = WRAP(malloc_zone_from_ptr)(ptr); \ |
| 245 | const char *zone_name = (zone_ptr == 0) ? 0 : zone_ptr->zone_name |
| 246 | |
| 247 | extern "C" |
| 248 | SANITIZER_INTERFACE_ATTRIBUTE |
| 249 | void *__sanitizer_mz_realloc(malloc_zone_t *zone, void *ptr, size_t new_size) { |
| 250 | if (!ptr) { |
| 251 | COMMON_MALLOC_MALLOC(new_size); |
| 252 | return p; |
| 253 | } else { |
| 254 | COMMON_MALLOC_SIZE(ptr); |
| 255 | if (size) { |
| 256 | COMMON_MALLOC_REALLOC(ptr, new_size); |
| 257 | return p; |
| 258 | } else { |
| 259 | // We can't recover from reallocating an unknown address, because |
| 260 | // this would require reading at most |new_size| bytes from |
| 261 | // potentially unaccessible memory. |
| 262 | GET_ZONE_FOR_PTR(ptr); |
| 263 | COMMON_MALLOC_REPORT_UNKNOWN_REALLOC(ptr, zone_ptr, zone_name); |
| 264 | return nullptr; |
| 265 | } |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | extern "C" |
| 270 | SANITIZER_INTERFACE_ATTRIBUTE |
| 271 | void __sanitizer_mz_destroy(malloc_zone_t* zone) { |
| 272 | // A no-op -- we will not be destroyed! |
| 273 | Report("__sanitizer_mz_destroy() called -- ignoring\n"); |
| 274 | } |
| 275 | |
| 276 | extern "C" |
| 277 | SANITIZER_INTERFACE_ATTRIBUTE |
| 278 | void *__sanitizer_mz_memalign(malloc_zone_t *zone, size_t align, size_t size) { |
| 279 | COMMON_MALLOC_ENTER(); |
| 280 | COMMON_MALLOC_MEMALIGN(align, size); |
| 281 | return p; |
| 282 | } |
| 283 | |
| 284 | // This public API exists purely for testing purposes. |
| 285 | extern "C" |
| 286 | SANITIZER_INTERFACE_ATTRIBUTE |
| 287 | malloc_zone_t* __sanitizer_mz_default_zone() { |
| 288 | return &sanitizer_zone; |
| 289 | } |
| 290 | |
| 291 | // This function is currently unused, and we build with -Werror. |
| 292 | #if 0 |
| 293 | void __sanitizer_mz_free_definite_size( |
| 294 | malloc_zone_t* zone, void *ptr, size_t size) { |
| 295 | // TODO(glider): check that |size| is valid. |
| 296 | UNIMPLEMENTED(); |
| 297 | } |
| 298 | #endif |
| 299 | |
| 300 | #ifndef COMMON_MALLOC_HAS_ZONE_ENUMERATOR |
| 301 | #error "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be defined" |
| 302 | #endif |
| 303 | static_assert((COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 0 || |
| 304 | (COMMON_MALLOC_HAS_ZONE_ENUMERATOR) == 1, |
| 305 | "COMMON_MALLOC_HAS_ZONE_ENUMERATOR must be 0 or 1"); |
| 306 | |
| 307 | #if COMMON_MALLOC_HAS_ZONE_ENUMERATOR |
| 308 | // Forward declare and expect the implementation to provided by |
| 309 | // includer. |
| 310 | kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, |
| 311 | vm_address_t zone_address, memory_reader_t reader, |
| 312 | vm_range_recorder_t recorder); |
| 313 | #else |
| 314 | // Provide stub implementation that fails. |
| 315 | kern_return_t mi_enumerator(task_t task, void *, unsigned type_mask, |
| 316 | vm_address_t zone_address, memory_reader_t reader, |
| 317 | vm_range_recorder_t recorder) { |
| 318 | // Not supported. |
| 319 | return KERN_FAILURE; |
| 320 | } |
| 321 | #endif |
| 322 | |
| 323 | #ifndef COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT |
| 324 | #error "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be defined" |
| 325 | #endif |
| 326 | static_assert((COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 0 || |
| 327 | (COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT) == 1, |
| 328 | "COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT must be 0 or 1"); |
| 329 | #if COMMON_MALLOC_HAS_EXTRA_INTROSPECTION_INIT |
| 330 | // Forward declare and expect the implementation to provided by |
| 331 | // includer. |
| 332 | void mi_extra_init( |
| 333 | sanitizer_malloc_introspection_t *mi); |
| 334 | #else |
| 335 | void mi_extra_init( |
| 336 | sanitizer_malloc_introspection_t *mi) { |
| 337 | // Just zero initialize the fields. |
| 338 | mi->allocator_ptr = 0; |
| 339 | mi->allocator_size = 0; |
| 340 | } |
| 341 | #endif |
| 342 | |
| 343 | size_t mi_good_size(malloc_zone_t *zone, size_t size) { |
| 344 | // I think it's always safe to return size, but we maybe could do better. |
| 345 | return size; |
| 346 | } |
| 347 | |
| 348 | boolean_t mi_check(malloc_zone_t *zone) { |
| 349 | UNIMPLEMENTED(); |
| 350 | } |
| 351 | |
| 352 | void mi_print(malloc_zone_t *zone, boolean_t verbose) { |
| 353 | UNIMPLEMENTED(); |
| 354 | } |
| 355 | |
| 356 | void mi_log(malloc_zone_t *zone, void *address) { |
| 357 | // I don't think we support anything like this |
| 358 | } |
| 359 | |
| 360 | void mi_force_lock(malloc_zone_t *zone) { |
| 361 | COMMON_MALLOC_FORCE_LOCK(); |
| 362 | } |
| 363 | |
| 364 | void mi_force_unlock(malloc_zone_t *zone) { |
| 365 | COMMON_MALLOC_FORCE_UNLOCK(); |
| 366 | } |
| 367 | |
| 368 | void mi_statistics(malloc_zone_t *zone, malloc_statistics_t *stats) { |
| 369 | COMMON_MALLOC_FILL_STATS(zone, stats); |
| 370 | } |
| 371 | |
| 372 | boolean_t mi_zone_locked(malloc_zone_t *zone) { |
| 373 | // UNIMPLEMENTED(); |
| 374 | return false; |
| 375 | } |
| 376 | |
| 377 | } // unnamed namespace |
| 378 | |
| 379 | namespace COMMON_MALLOC_NAMESPACE { |
| 380 | |
| 381 | void InitMallocZoneFields() { |
| 382 | static sanitizer_malloc_introspection_t sanitizer_zone_introspection; |
| 383 | // Ok to use internal_memset, these places are not performance-critical. |
| 384 | internal_memset(&sanitizer_zone_introspection, 0, |
| 385 | sizeof(sanitizer_zone_introspection)); |
| 386 | |
| 387 | sanitizer_zone_introspection.enumerator = &mi_enumerator; |
| 388 | sanitizer_zone_introspection.good_size = &mi_good_size; |
| 389 | sanitizer_zone_introspection.check = &mi_check; |
| 390 | sanitizer_zone_introspection.print = &mi_print; |
| 391 | sanitizer_zone_introspection.log = &mi_log; |
| 392 | sanitizer_zone_introspection.force_lock = &mi_force_lock; |
| 393 | sanitizer_zone_introspection.force_unlock = &mi_force_unlock; |
| 394 | sanitizer_zone_introspection.statistics = &mi_statistics; |
| 395 | sanitizer_zone_introspection.zone_locked = &mi_zone_locked; |
| 396 | |
| 397 | // Set current allocator enumeration version. |
| 398 | sanitizer_zone_introspection.allocator_enumeration_version = |
| 399 | GetMallocZoneAllocatorEnumerationVersion(); |
| 400 | |
| 401 | // Perform any sanitizer specific initialization. |
| 402 | mi_extra_init(&sanitizer_zone_introspection); |
| 403 | |
| 404 | internal_memset(&sanitizer_zone, 0, sizeof(malloc_zone_t)); |
| 405 | |
| 406 | // Use version 6 for OSX >= 10.6. |
| 407 | sanitizer_zone.version = 6; |
| 408 | sanitizer_zone.zone_name = COMMON_MALLOC_ZONE_NAME; |
| 409 | sanitizer_zone.size = &__sanitizer_mz_size; |
| 410 | sanitizer_zone.malloc = &__sanitizer_mz_malloc; |
| 411 | sanitizer_zone.calloc = &__sanitizer_mz_calloc; |
| 412 | sanitizer_zone.valloc = &__sanitizer_mz_valloc; |
| 413 | sanitizer_zone.free = &__sanitizer_mz_free; |
| 414 | sanitizer_zone.realloc = &__sanitizer_mz_realloc; |
| 415 | sanitizer_zone.destroy = &__sanitizer_mz_destroy; |
| 416 | sanitizer_zone.batch_malloc = 0; |
| 417 | sanitizer_zone.batch_free = 0; |
| 418 | sanitizer_zone.free_definite_size = 0; |
| 419 | sanitizer_zone.memalign = &__sanitizer_mz_memalign; |
| 420 | sanitizer_zone.introspect = &sanitizer_zone_introspection; |
| 421 | } |
| 422 | |
| 423 | void ReplaceSystemMalloc() { |
| 424 | InitMallocZoneFields(); |
| 425 | |
| 426 | // Register the zone. |
| 427 | malloc_zone_register(&sanitizer_zone); |
| 428 | } |
| 429 | |
| 430 | } // namespace COMMON_MALLOC_NAMESPACE |