| OLD | NEW |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // This file contains all the logic necessary to intercept allocations on | 5 // This file contains all the logic necessary to intercept allocations on |
| 6 // macOS. "malloc zones" are an abstraction that allows the process to intercept | 6 // macOS. "malloc zones" are an abstraction that allows the process to intercept |
| 7 // all malloc-related functions. There is no good mechanism [short of | 7 // all malloc-related functions. There is no good mechanism [short of |
| 8 // interposition] to determine new malloc zones are added, so there's no clean | 8 // interposition] to determine new malloc zones are added, so there's no clean |
| 9 // mechanism to intercept all malloc zones. This file contains logic to | 9 // mechanism to intercept all malloc zones. This file contains logic to |
| 10 // intercept the default and purgeable zones, which always exist. A cursory | 10 // intercept the default and purgeable zones, which always exist. A cursory |
| 11 // review of Chrome seems to imply that non-default zones are almost never used. | 11 // review of Chrome seems to imply that non-default zones are almost never used. |
| 12 // | 12 // |
| 13 // This file also contains logic to intercept Core Foundation and Objective-C | 13 // This file also contains logic to intercept Core Foundation and Objective-C |
| 14 // allocations. The implementations forward to the default malloc zone, so the | 14 // allocations. The implementations forward to the default malloc zone, so the |
| 15 // only reason to intercept these calls is to re-label OOM crashes with slightly | 15 // only reason to intercept these calls is to re-label OOM crashes with slightly |
| 16 // more details. | 16 // more details. |
| 17 | 17 |
| 18 #include "base/allocator/allocator_interception_mac.h" | 18 #include "base/allocator/allocator_interception_mac.h" |
| 19 | 19 |
| 20 #include <CoreFoundation/CoreFoundation.h> | 20 #include <CoreFoundation/CoreFoundation.h> |
| 21 #import <Foundation/Foundation.h> | 21 #import <Foundation/Foundation.h> |
| 22 #include <errno.h> | 22 #include <errno.h> |
| 23 #include <mach/mach.h> | 23 #include <mach/mach.h> |
| 24 #include <mach/mach_vm.h> | 24 #include <mach/mach_vm.h> |
| 25 #import <objc/runtime.h> | 25 #import <objc/runtime.h> |
| 26 #include <stddef.h> | 26 #include <stddef.h> |
| 27 | 27 |
| 28 #include <new> | 28 #include <new> |
| 29 | 29 |
| 30 #include "base/allocator/allocator_shim.h" | |
| 31 #include "base/allocator/features.h" | 30 #include "base/allocator/features.h" |
| 32 #include "base/allocator/malloc_zone_functions_mac.h" | 31 #include "base/allocator/malloc_zone_functions_mac.h" |
| 33 #include "base/logging.h" | 32 #include "base/logging.h" |
| 34 #include "base/mac/mac_util.h" | 33 #include "base/mac/mac_util.h" |
| 35 #include "base/mac/mach_logging.h" | 34 #include "base/mac/mach_logging.h" |
| 36 #include "base/process/memory.h" | 35 #include "base/process/memory.h" |
| 37 #include "base/scoped_clear_errno.h" | 36 #include "base/scoped_clear_errno.h" |
| 38 #include "build/build_config.h" | 37 #include "build/build_config.h" |
| 39 #include "third_party/apple_apsl/CFBase.h" | 38 #include "third_party/apple_apsl/CFBase.h" |
| 40 | 39 |
| (...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 257 typedef id (*allocWithZone_t)(id, SEL, NSZone*); | 256 typedef id (*allocWithZone_t)(id, SEL, NSZone*); |
| 258 allocWithZone_t g_old_allocWithZone; | 257 allocWithZone_t g_old_allocWithZone; |
| 259 | 258 |
| 260 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) { | 259 id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) { |
| 261 id result = g_old_allocWithZone(self, _cmd, zone); | 260 id result = g_old_allocWithZone(self, _cmd, zone); |
| 262 if (!result) | 261 if (!result) |
| 263 TerminateBecauseOutOfMemory(0); | 262 TerminateBecauseOutOfMemory(0); |
| 264 return result; | 263 return result; |
| 265 } | 264 } |
| 266 | 265 |
| 267 } // namespace | |
| 268 | |
| 269 bool UncheckedMallocMac(size_t size, void** result) { | |
| 270 #if defined(ADDRESS_SANITIZER) | |
| 271 *result = malloc(size); | |
| 272 #else | |
| 273 if (g_old_zone.malloc) { | |
| 274 *result = g_old_zone.malloc(malloc_default_zone(), size); | |
| 275 } else { | |
| 276 *result = malloc(size); | |
| 277 } | |
| 278 #endif // defined(ADDRESS_SANITIZER) | |
| 279 | |
| 280 return *result != NULL; | |
| 281 } | |
| 282 | |
| 283 bool UncheckedCallocMac(size_t num_items, size_t size, void** result) { | |
| 284 #if defined(ADDRESS_SANITIZER) | |
| 285 *result = calloc(num_items, size); | |
| 286 #else | |
| 287 if (g_old_zone.calloc) { | |
| 288 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); | |
| 289 } else { | |
| 290 *result = calloc(num_items, size); | |
| 291 } | |
| 292 #endif // defined(ADDRESS_SANITIZER) | |
| 293 | |
| 294 return *result != NULL; | |
| 295 } | |
| 296 | |
| 297 void ReplaceZoneFunctions(ChromeMallocZone* zone, | 266 void ReplaceZoneFunctions(ChromeMallocZone* zone, |
| 298 const MallocZoneFunctions* functions) { | 267 const MallocZoneFunctions* functions) { |
| 299 // Remove protection. | 268 // Remove protection. |
| 300 mach_vm_address_t reprotection_start = 0; | 269 mach_vm_address_t reprotection_start = 0; |
| 301 mach_vm_size_t reprotection_length = 0; | 270 mach_vm_size_t reprotection_length = 0; |
| 302 vm_prot_t reprotection_value = VM_PROT_NONE; | 271 vm_prot_t reprotection_value = VM_PROT_NONE; |
| 303 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, | 272 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, |
| 304 &reprotection_value); | 273 &reprotection_value); |
| 305 | 274 |
| 306 CHECK(functions->malloc && functions->calloc && functions->valloc && | 275 CHECK(functions->malloc && functions->calloc && functions->valloc && |
| (...skipping 18 matching lines...) Expand all Loading... |
| 325 | 294 |
| 326 // Restore protection if it was active. | 295 // Restore protection if it was active. |
| 327 if (reprotection_start) { | 296 if (reprotection_start) { |
| 328 kern_return_t result = | 297 kern_return_t result = |
| 329 mach_vm_protect(mach_task_self(), reprotection_start, | 298 mach_vm_protect(mach_task_self(), reprotection_start, |
| 330 reprotection_length, false, reprotection_value); | 299 reprotection_length, false, reprotection_value); |
| 331 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; | 300 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; |
| 332 } | 301 } |
| 333 } | 302 } |
| 334 | 303 |
| 304 void UninterceptMallocZoneForTesting(struct _malloc_zone_t* zone) { |
| 305 ChromeMallocZone* chrome_zone = reinterpret_cast<ChromeMallocZone*>(zone); |
| 306 if (!IsMallocZoneAlreadyStored(chrome_zone)) |
| 307 return; |
| 308 MallocZoneFunctions& functions = GetFunctionsForZone(zone); |
| 309 ReplaceZoneFunctions(chrome_zone, &functions); |
| 310 } |
| 311 |
| 312 } // namespace |
| 313 |
| 314 bool UncheckedMallocMac(size_t size, void** result) { |
| 315 #if defined(ADDRESS_SANITIZER) |
| 316 *result = malloc(size); |
| 317 #else |
| 318 if (g_old_zone.malloc) { |
| 319 *result = g_old_zone.malloc(malloc_default_zone(), size); |
| 320 } else { |
| 321 *result = malloc(size); |
| 322 } |
| 323 #endif // defined(ADDRESS_SANITIZER) |
| 324 |
| 325 return *result != NULL; |
| 326 } |
| 327 |
| 328 bool UncheckedCallocMac(size_t num_items, size_t size, void** result) { |
| 329 #if defined(ADDRESS_SANITIZER) |
| 330 *result = calloc(num_items, size); |
| 331 #else |
| 332 if (g_old_zone.calloc) { |
| 333 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); |
| 334 } else { |
| 335 *result = calloc(num_items, size); |
| 336 } |
| 337 #endif // defined(ADDRESS_SANITIZER) |
| 338 |
| 339 return *result != NULL; |
| 340 } |
| 341 |
| 335 void StoreFunctionsForDefaultZone() { | 342 void StoreFunctionsForDefaultZone() { |
| 336 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( | 343 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( |
| 337 malloc_default_zone()); | 344 malloc_default_zone()); |
| 338 StoreMallocZone(default_zone); | 345 StoreMallocZone(default_zone); |
| 339 } | 346 } |
| 340 | 347 |
| 341 void StoreFunctionsForAllZones() { | 348 void StoreFunctionsForAllZones() { |
| 342 // This ensures that the default zone is always at the front of the array, | 349 // This ensures that the default zone is always at the front of the array, |
| 343 // which is important for performance. | 350 // which is important for performance. |
| 344 StoreFunctionsForDefaultZone(); | 351 StoreFunctionsForDefaultZone(); |
| 345 | 352 |
| 346 vm_address_t* zones; | 353 vm_address_t* zones; |
| 347 unsigned int count; | 354 unsigned int count; |
| 348 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); | 355 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); |
| 349 if (kr != KERN_SUCCESS) | 356 if (kr != KERN_SUCCESS) |
| 350 return; | 357 return; |
| 351 for (unsigned int i = 0; i < count; ++i) { | 358 for (unsigned int i = 0; i < count; ++i) { |
| 352 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | 359 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); |
| 353 StoreMallocZone(zone); | 360 StoreMallocZone(zone); |
| 354 } | 361 } |
| 355 } | 362 } |
| 356 | 363 |
| 357 void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) { | 364 void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) { |
| 365 // The default zone does not get returned in malloc_get_all_zones(). |
| 366 ChromeMallocZone* default_zone = |
| 367 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); |
| 368 if (DoesMallocZoneNeedReplacing(default_zone, functions)) { |
| 369 ReplaceZoneFunctions(default_zone, functions); |
| 370 } |
| 371 |
| 358 vm_address_t* zones; | 372 vm_address_t* zones; |
| 359 unsigned int count; | 373 unsigned int count; |
| 360 kern_return_t kr = | 374 kern_return_t kr = |
| 361 malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count); | 375 malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count); |
| 362 if (kr != KERN_SUCCESS) | 376 if (kr != KERN_SUCCESS) |
| 363 return; | 377 return; |
| 364 for (unsigned int i = 0; i < count; ++i) { | 378 for (unsigned int i = 0; i < count; ++i) { |
| 365 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | 379 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); |
| 366 if (IsMallocZoneAlreadyStored(zone) && zone->malloc != functions->malloc) { | 380 if (DoesMallocZoneNeedReplacing(zone, functions)) { |
| 367 ReplaceZoneFunctions(zone, functions); | 381 ReplaceZoneFunctions(zone, functions); |
| 368 } | 382 } |
| 369 } | 383 } |
| 370 g_replaced_default_zone = true; | 384 g_replaced_default_zone = true; |
| 371 } | 385 } |
| 372 | 386 |
| 373 void InterceptAllocationsMac() { | 387 void InterceptAllocationsMac() { |
| 374 if (g_oom_killer_enabled) | 388 if (g_oom_killer_enabled) |
| 375 return; | 389 return; |
| 376 | 390 |
| 377 g_oom_killer_enabled = true; | 391 g_oom_killer_enabled = true; |
| 378 | 392 |
| 379 // === C malloc/calloc/valloc/realloc/posix_memalign === | 393 // === C malloc/calloc/valloc/realloc/posix_memalign === |
| 380 | 394 |
| 381 // This approach is not perfect, as requests for amounts of memory larger than | 395 // This approach is not perfect, as requests for amounts of memory larger than |
| 382 // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will | 396 // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will |
| 383 // still fail with a NULL rather than dying (see | 397 // still fail with a NULL rather than dying (see |
| 384 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). | 398 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). |
| 385 // Unfortunately, it's the best we can do. Also note that this does not affect | 399 // Unfortunately, it's the best we can do. Also note that this does not affect |
| 386 // allocations from non-default zones. | 400 // allocations from non-default zones. |
| 387 | 401 |
| 388 #if !defined(ADDRESS_SANITIZER) | 402 #if !defined(ADDRESS_SANITIZER) |
| 389 // Don't do anything special on OOM for the malloc zones replaced by | 403 // Don't do anything special on OOM for the malloc zones replaced by |
| 390 // AddressSanitizer, as modifying or protecting them may not work correctly. | 404 // AddressSanitizer, as modifying or protecting them may not work correctly. |
| 391 ChromeMallocZone* default_zone = | 405 ChromeMallocZone* default_zone = |
| 392 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); | 406 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); |
| 393 if (!IsMallocZoneAlreadyStored(default_zone)) { | 407 if (!IsMallocZoneAlreadyStored(default_zone)) { |
| 394 StoreZoneFunctions(default_zone, &g_old_zone); | 408 StoreZoneFunctions(default_zone, &g_old_zone); |
| 395 MallocZoneFunctions new_functions; | 409 MallocZoneFunctions new_functions = {}; |
| 396 new_functions.malloc = oom_killer_malloc; | 410 new_functions.malloc = oom_killer_malloc; |
| 397 new_functions.calloc = oom_killer_calloc; | 411 new_functions.calloc = oom_killer_calloc; |
| 398 new_functions.valloc = oom_killer_valloc; | 412 new_functions.valloc = oom_killer_valloc; |
| 399 new_functions.free = oom_killer_free; | 413 new_functions.free = oom_killer_free; |
| 400 new_functions.realloc = oom_killer_realloc; | 414 new_functions.realloc = oom_killer_realloc; |
| 401 new_functions.memalign = oom_killer_memalign; | 415 new_functions.memalign = oom_killer_memalign; |
| 402 | 416 |
| 403 ReplaceZoneFunctions(default_zone, &new_functions); | 417 ReplaceZoneFunctions(default_zone, &new_functions); |
| 404 g_replaced_default_zone = true; | 418 g_replaced_default_zone = true; |
| 405 } | 419 } |
| 406 | 420 |
| 407 ChromeMallocZone* purgeable_zone = | 421 ChromeMallocZone* purgeable_zone = |
| 408 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); | 422 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); |
| 409 if (purgeable_zone && !IsMallocZoneAlreadyStored(purgeable_zone)) { | 423 if (purgeable_zone && !IsMallocZoneAlreadyStored(purgeable_zone)) { |
| 410 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); | 424 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); |
| 411 MallocZoneFunctions new_functions; | 425 MallocZoneFunctions new_functions = {}; |
| 412 new_functions.malloc = oom_killer_malloc_purgeable; | 426 new_functions.malloc = oom_killer_malloc_purgeable; |
| 413 new_functions.calloc = oom_killer_calloc_purgeable; | 427 new_functions.calloc = oom_killer_calloc_purgeable; |
| 414 new_functions.valloc = oom_killer_valloc_purgeable; | 428 new_functions.valloc = oom_killer_valloc_purgeable; |
| 415 new_functions.free = oom_killer_free_purgeable; | 429 new_functions.free = oom_killer_free_purgeable; |
| 416 new_functions.realloc = oom_killer_realloc_purgeable; | 430 new_functions.realloc = oom_killer_realloc_purgeable; |
| 417 new_functions.memalign = oom_killer_memalign_purgeable; | 431 new_functions.memalign = oom_killer_memalign_purgeable; |
| 418 ReplaceZoneFunctions(purgeable_zone, &new_functions); | 432 ReplaceZoneFunctions(purgeable_zone, &new_functions); |
| 419 } | 433 } |
| 420 #endif | 434 #endif |
| 421 | 435 |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 489 Method orig_method = | 503 Method orig_method = |
| 490 class_getClassMethod(nsobject_class, @selector(allocWithZone:)); | 504 class_getClassMethod(nsobject_class, @selector(allocWithZone:)); |
| 491 g_old_allocWithZone = | 505 g_old_allocWithZone = |
| 492 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); | 506 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); |
| 493 CHECK(g_old_allocWithZone) | 507 CHECK(g_old_allocWithZone) |
| 494 << "Failed to get allocWithZone allocation function."; | 508 << "Failed to get allocWithZone allocation function."; |
| 495 method_setImplementation(orig_method, | 509 method_setImplementation(orig_method, |
| 496 reinterpret_cast<IMP>(oom_killer_allocWithZone)); | 510 reinterpret_cast<IMP>(oom_killer_allocWithZone)); |
| 497 } | 511 } |
| 498 | 512 |
| 513 void UninterceptMallocZonesForTesting() { |
| 514 UninterceptMallocZoneForTesting(malloc_default_zone()); |
| 515 vm_address_t* zones; |
| 516 unsigned int count; |
| 517 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); |
| 518 CHECK(kr == KERN_SUCCESS); |
| 519 for (unsigned int i = 0; i < count; ++i) { |
| 520 UninterceptMallocZoneForTesting( |
| 521 reinterpret_cast<struct _malloc_zone_t*>(zones[i])); |
| 522 } |
| 523 |
| 524 ClearAllMallocZonesForTesting(); |
| 525 } |
| 526 |
| 499 } // namespace allocator | 527 } // namespace allocator |
| 500 } // namespace base | 528 } // namespace base |
| OLD | NEW |