Chromium Code Reviews| 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 |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 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" | 30 #include "base/allocator/allocator_shim.h" |
| 31 #include "base/allocator/allocator_shim_default_dispatch_to_mac_zoned_malloc.h" | |
|
Primiano Tucci (use gerrit)
2017/02/22 12:25:39
What is this for? We shouldn't have cyclic logical
erikchen
2017/02/22 21:03:09
whoops, unnecessary include.
| |
| 31 #include "base/allocator/features.h" | 32 #include "base/allocator/features.h" |
| 32 #include "base/logging.h" | 33 #include "base/logging.h" |
| 33 #include "base/mac/mac_util.h" | 34 #include "base/mac/mac_util.h" |
| 34 #include "base/mac/mach_logging.h" | 35 #include "base/mac/mach_logging.h" |
| 35 #include "base/process/memory.h" | 36 #include "base/process/memory.h" |
| 36 #include "base/scoped_clear_errno.h" | 37 #include "base/scoped_clear_errno.h" |
| 37 #include "build/build_config.h" | 38 #include "build/build_config.h" |
| 38 #include "third_party/apple_apsl/CFBase.h" | 39 #include "third_party/apple_apsl/CFBase.h" |
| 39 | 40 |
| 40 namespace base { | 41 namespace base { |
| 41 namespace allocator { | 42 namespace allocator { |
| 42 | 43 |
| 43 bool g_replaced_default_zone = false; | 44 bool g_replaced_default_zone = false; |
| 44 | 45 |
| 45 MallocZoneFunctions::MallocZoneFunctions() {} | |
| 46 | |
| 47 namespace { | 46 namespace { |
| 48 | 47 |
| 49 bool g_oom_killer_enabled; | 48 bool g_oom_killer_enabled; |
| 50 | 49 |
| 51 // Starting with Mac OS X 10.7, the zone allocators set up by the system are | 50 // Starting with Mac OS X 10.7, the zone allocators set up by the system are |
| 52 // read-only, to prevent them from being overwritten in an attack. However, | 51 // read-only, to prevent them from being overwritten in an attack. However, |
| 53 // blindly unprotecting and reprotecting the zone allocators fails with | 52 // blindly unprotecting and reprotecting the zone allocators fails with |
| 54 // GuardMalloc because GuardMalloc sets up its zone allocator using a block of | 53 // GuardMalloc because GuardMalloc sets up its zone allocator using a block of |
| 55 // memory in its bss. Explicit saving/restoring of the protection is required. | 54 // memory in its bss. Explicit saving/restoring of the protection is required. |
| 56 // | 55 // |
| (...skipping 231 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 288 if (g_old_zone.calloc) { | 287 if (g_old_zone.calloc) { |
| 289 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); | 288 *result = g_old_zone.calloc(malloc_default_zone(), num_items, size); |
| 290 } else { | 289 } else { |
| 291 *result = calloc(num_items, size); | 290 *result = calloc(num_items, size); |
| 292 } | 291 } |
| 293 #endif // defined(ADDRESS_SANITIZER) | 292 #endif // defined(ADDRESS_SANITIZER) |
| 294 | 293 |
| 295 return *result != NULL; | 294 return *result != NULL; |
| 296 } | 295 } |
| 297 | 296 |
| 298 void StoreZoneFunctions(ChromeMallocZone* zone, | |
| 299 MallocZoneFunctions* functions) { | |
| 300 functions->malloc = zone->malloc; | |
| 301 functions->calloc = zone->calloc; | |
| 302 functions->valloc = zone->valloc; | |
| 303 functions->free = zone->free; | |
| 304 functions->realloc = zone->realloc; | |
| 305 functions->size = zone->size; | |
| 306 CHECK(functions->malloc && functions->calloc && functions->valloc && | |
| 307 functions->free && functions->realloc && functions->size); | |
| 308 | |
| 309 // These functions might be nullptr. | |
| 310 functions->batch_malloc = zone->batch_malloc; | |
| 311 functions->batch_free = zone->batch_free; | |
| 312 | |
| 313 if (zone->version >= 5) { | |
| 314 functions->memalign = zone->memalign; | |
| 315 CHECK(functions->memalign); | |
| 316 } | |
| 317 if (zone->version >= 6) { | |
| 318 // This may be nullptr. | |
| 319 functions->free_definite_size = zone->free_definite_size; | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 void ReplaceZoneFunctions(ChromeMallocZone* zone, | 297 void ReplaceZoneFunctions(ChromeMallocZone* zone, |
| 324 const MallocZoneFunctions* functions) { | 298 const MallocZoneFunctions* functions) { |
| 325 // Remove protection. | 299 // Remove protection. |
| 326 mach_vm_address_t reprotection_start = 0; | 300 mach_vm_address_t reprotection_start = 0; |
| 327 mach_vm_size_t reprotection_length = 0; | 301 mach_vm_size_t reprotection_length = 0; |
| 328 vm_prot_t reprotection_value = VM_PROT_NONE; | 302 vm_prot_t reprotection_value = VM_PROT_NONE; |
| 329 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, | 303 DeprotectMallocZone(zone, &reprotection_start, &reprotection_length, |
| 330 &reprotection_value); | 304 &reprotection_value); |
| 331 | 305 |
| 332 CHECK(functions->malloc && functions->calloc && functions->valloc && | 306 CHECK(functions->malloc && functions->calloc && functions->valloc && |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 351 | 325 |
| 352 // Restore protection if it was active. | 326 // Restore protection if it was active. |
| 353 if (reprotection_start) { | 327 if (reprotection_start) { |
| 354 kern_return_t result = | 328 kern_return_t result = |
| 355 mach_vm_protect(mach_task_self(), reprotection_start, | 329 mach_vm_protect(mach_task_self(), reprotection_start, |
| 356 reprotection_length, false, reprotection_value); | 330 reprotection_length, false, reprotection_value); |
| 357 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; | 331 MACH_CHECK(result == KERN_SUCCESS, result) << "mach_vm_protect"; |
| 358 } | 332 } |
| 359 } | 333 } |
| 360 | 334 |
| 361 void StoreFunctionsForDefaultZone(MallocZoneFunctions* functions) { | 335 void StoreFunctionsForDefaultZone(MallocZoneAggregator* aggregator) { |
| 362 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( | 336 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( |
| 363 malloc_default_zone()); | 337 malloc_default_zone()); |
| 364 StoreZoneFunctions(default_zone, functions); | 338 aggregator->StoreZone(default_zone); |
| 365 } | 339 } |
| 366 | 340 |
| 367 void ReplaceFunctionsForDefaultZone(const MallocZoneFunctions* functions) { | 341 void StoreFunctionsForAllZones(MallocZoneAggregator* aggregator) { |
| 368 CHECK(!g_replaced_default_zone); | 342 // This ensures that the default zone is always at the front of the |
| 343 // aggregator, which is important for performance. | |
| 344 StoreFunctionsForDefaultZone(aggregator); | |
| 345 | |
| 346 vm_address_t* zones; | |
| 347 unsigned int count; | |
| 348 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); | |
| 349 if (kr != KERN_SUCCESS) | |
| 350 return; | |
| 351 for (unsigned int i = 0; i < count; ++i) { | |
| 352 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | |
| 353 aggregator->StoreZone(zone); | |
| 354 } | |
| 355 } | |
| 356 | |
| 357 void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) { | |
| 358 vm_address_t* zones; | |
| 359 unsigned int count; | |
| 360 kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count); | |
| 361 if (kr != KERN_SUCCESS) | |
| 362 return; | |
| 363 for (unsigned int i = 0; i < count; ++i) { | |
| 364 ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]); | |
| 365 if (!MallocZoneAggregator::GetMallocZoneAggregator()->IsZoneAlreadyStored( | |
| 366 zone)) { | |
| 367 continue; | |
| 368 } | |
| 369 if (zone->malloc == functions->malloc) | |
| 370 continue; | |
| 371 ReplaceZoneFunctions(zone, functions); | |
| 372 } | |
| 369 g_replaced_default_zone = true; | 373 g_replaced_default_zone = true; |
| 370 #if !defined(ADDRESS_SANITIZER) | |
| 371 StoreFunctionsForDefaultZone(&g_old_zone); | |
| 372 #endif | |
| 373 ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>( | |
| 374 malloc_default_zone()); | |
| 375 ReplaceZoneFunctions(default_zone, functions); | |
| 376 } | 374 } |
| 377 | 375 |
| 378 void InterceptAllocationsMac() { | 376 void InterceptAllocationsMac() { |
| 379 if (g_oom_killer_enabled) | 377 if (g_oom_killer_enabled) |
| 380 return; | 378 return; |
| 381 | 379 |
| 382 g_oom_killer_enabled = true; | 380 g_oom_killer_enabled = true; |
| 383 | 381 |
| 384 // === C malloc/calloc/valloc/realloc/posix_memalign === | 382 // === C malloc/calloc/valloc/realloc/posix_memalign === |
| 385 | 383 |
| 386 // This approach is not perfect, as requests for amounts of memory larger than | 384 // This approach is not perfect, as requests for amounts of memory larger than |
| 387 // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will | 385 // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will |
| 388 // still fail with a NULL rather than dying (see | 386 // still fail with a NULL rather than dying (see |
| 389 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). | 387 // http://opensource.apple.com/source/Libc/Libc-583/gen/malloc.c for details). |
| 390 // Unfortunately, it's the best we can do. Also note that this does not affect | 388 // Unfortunately, it's the best we can do. Also note that this does not affect |
| 391 // allocations from non-default zones. | 389 // allocations from non-default zones. |
| 392 | 390 |
| 393 #if !defined(ADDRESS_SANITIZER) | 391 #if !defined(ADDRESS_SANITIZER) |
| 394 // Don't do anything special on OOM for the malloc zones replaced by | 392 // Don't do anything special on OOM for the malloc zones replaced by |
| 395 // AddressSanitizer, as modifying or protecting them may not work correctly. | 393 // AddressSanitizer, as modifying or protecting them may not work correctly. |
| 396 if (!g_replaced_default_zone) { | 394 ChromeMallocZone* default_zone = |
| 397 StoreFunctionsForDefaultZone(&g_old_zone); | 395 reinterpret_cast<ChromeMallocZone*>(malloc_default_zone()); |
| 396 if (!MallocZoneAggregator::GetMallocZoneAggregator()->IsZoneAlreadyStored( | |
| 397 default_zone)) { | |
| 398 StoreZoneFunctions(default_zone, &g_old_zone); | |
| 398 MallocZoneFunctions new_functions; | 399 MallocZoneFunctions new_functions; |
| 399 new_functions.malloc = oom_killer_malloc; | 400 new_functions.malloc = oom_killer_malloc; |
| 400 new_functions.calloc = oom_killer_calloc; | 401 new_functions.calloc = oom_killer_calloc; |
| 401 new_functions.valloc = oom_killer_valloc; | 402 new_functions.valloc = oom_killer_valloc; |
| 402 new_functions.free = oom_killer_free; | 403 new_functions.free = oom_killer_free; |
| 403 new_functions.realloc = oom_killer_realloc; | 404 new_functions.realloc = oom_killer_realloc; |
| 404 new_functions.memalign = oom_killer_memalign; | 405 new_functions.memalign = oom_killer_memalign; |
| 405 ReplaceFunctionsForDefaultZone(&new_functions); | 406 |
| 407 ReplaceZoneFunctions(default_zone, &new_functions); | |
| 408 g_replaced_default_zone = true; | |
| 406 } | 409 } |
| 407 | 410 |
| 408 ChromeMallocZone* purgeable_zone = | 411 ChromeMallocZone* purgeable_zone = |
| 409 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); | 412 reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone()); |
| 410 if (purgeable_zone) { | 413 if (purgeable_zone && |
| 414 !MallocZoneAggregator::GetMallocZoneAggregator()->IsZoneAlreadyStored( | |
| 415 purgeable_zone)) { | |
| 411 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); | 416 StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone); |
| 412 MallocZoneFunctions new_functions; | 417 MallocZoneFunctions new_functions; |
| 413 new_functions.malloc = oom_killer_malloc_purgeable; | 418 new_functions.malloc = oom_killer_malloc_purgeable; |
| 414 new_functions.calloc = oom_killer_calloc_purgeable; | 419 new_functions.calloc = oom_killer_calloc_purgeable; |
| 415 new_functions.valloc = oom_killer_valloc_purgeable; | 420 new_functions.valloc = oom_killer_valloc_purgeable; |
| 416 new_functions.free = oom_killer_free_purgeable; | 421 new_functions.free = oom_killer_free_purgeable; |
| 417 new_functions.realloc = oom_killer_realloc_purgeable; | 422 new_functions.realloc = oom_killer_realloc_purgeable; |
| 418 new_functions.memalign = oom_killer_memalign_purgeable; | 423 new_functions.memalign = oom_killer_memalign_purgeable; |
| 419 ReplaceZoneFunctions(purgeable_zone, &new_functions); | 424 ReplaceZoneFunctions(purgeable_zone, &new_functions); |
| 420 } | 425 } |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 492 g_old_allocWithZone = | 497 g_old_allocWithZone = |
| 493 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); | 498 reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method)); |
| 494 CHECK(g_old_allocWithZone) | 499 CHECK(g_old_allocWithZone) |
| 495 << "Failed to get allocWithZone allocation function."; | 500 << "Failed to get allocWithZone allocation function."; |
| 496 method_setImplementation(orig_method, | 501 method_setImplementation(orig_method, |
| 497 reinterpret_cast<IMP>(oom_killer_allocWithZone)); | 502 reinterpret_cast<IMP>(oom_killer_allocWithZone)); |
| 498 } | 503 } |
| 499 | 504 |
| 500 } // namespace allocator | 505 } // namespace allocator |
| 501 } // namespace base | 506 } // namespace base |
| OLD | NEW |