allocator_interception_mac.mm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  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
  3. // found in the LICENSE file.
  4. // This file contains all the logic necessary to intercept allocations on
  5. // macOS. "malloc zones" are an abstraction that allows the process to intercept
  6. // all malloc-related functions. There is no good mechanism [short of
  7. // interposition] to determine new malloc zones are added, so there's no clean
  8. // mechanism to intercept all malloc zones. This file contains logic to
  9. // intercept the default and purgeable zones, which always exist. A cursory
  10. // review of Chrome seems to imply that non-default zones are almost never used.
  11. //
  12. // This file also contains logic to intercept Core Foundation and Objective-C
  13. // allocations. The implementations forward to the default malloc zone, so the
  14. // only reason to intercept these calls is to re-label OOM crashes with slightly
  15. // more details.
  16. #include "base/allocator/allocator_interception_mac.h"
  17. #include <CoreFoundation/CoreFoundation.h>
  18. #import <Foundation/Foundation.h>
  19. #include <errno.h>
  20. #include <mach/mach.h>
  21. #import <objc/runtime.h>
  22. #include <stddef.h>
  23. #include <new>
  24. #include "base/allocator/buildflags.h"
  25. #include "base/allocator/malloc_zone_functions_mac.h"
  26. #include "base/bind.h"
  27. #include "base/bits.h"
  28. #include "base/logging.h"
  29. #include "base/mac/mach_logging.h"
  30. #include "base/process/memory.h"
  31. #include "base/threading/sequenced_task_runner_handle.h"
  32. #include "base/time/time.h"
  33. #include "build/build_config.h"
  34. #include "third_party/apple_apsl/CFBase.h"
  35. #if BUILDFLAG(IS_IOS)
  36. #include "base/ios/ios_util.h"
  37. #else
  38. #include "base/mac/mac_util.h"
  39. #endif
  40. namespace base::allocator {
  41. bool g_replaced_default_zone = false;
  42. namespace {
  43. bool g_oom_killer_enabled;
  44. bool g_allocator_shims_failed_to_install;
  45. // Starting with Mac OS X 10.7, the zone allocators set up by the system are
  46. // read-only, to prevent them from being overwritten in an attack. However,
  47. // blindly unprotecting and reprotecting the zone allocators fails with
  48. // GuardMalloc because GuardMalloc sets up its zone allocator using a block of
  49. // memory in its bss. Explicit saving/restoring of the protection is required.
  50. //
  51. // This function takes a pointer to a malloc zone, de-protects it if necessary,
  52. // and returns (in the out parameters) a region of memory (if any) to be
  53. // re-protected when modifications are complete. This approach assumes that
  54. // there is no contention for the protection of this memory.
  55. //
  56. // Returns true if the malloc zone was properly de-protected, or false
  57. // otherwise. If this function returns false, the out parameters are invalid and
  58. // the region does not need to be re-protected.
  59. bool DeprotectMallocZone(ChromeMallocZone* default_zone,
  60. vm_address_t* reprotection_start,
  61. vm_size_t* reprotection_length,
  62. vm_prot_t* reprotection_value) {
  63. mach_port_t unused;
  64. *reprotection_start = reinterpret_cast<vm_address_t>(default_zone);
  65. struct vm_region_basic_info_64 info;
  66. mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
  67. kern_return_t result =
  68. vm_region_64(mach_task_self(), reprotection_start, reprotection_length,
  69. VM_REGION_BASIC_INFO_64,
  70. reinterpret_cast<vm_region_info_t>(&info), &count, &unused);
  71. if (result != KERN_SUCCESS) {
  72. MACH_LOG(ERROR, result) << "vm_region_64";
  73. return false;
  74. }
  75. // The kernel always returns a null object for VM_REGION_BASIC_INFO_64, but
  76. // balance it with a deallocate in case this ever changes. See
  77. // the VM_REGION_BASIC_INFO_64 case in vm_map_region() in 10.15's
  78. // https://opensource.apple.com/source/xnu/xnu-6153.11.26/osfmk/vm/vm_map.c .
  79. mach_port_deallocate(mach_task_self(), unused);
  80. if (!(info.max_protection & VM_PROT_WRITE)) {
  81. LOG(ERROR) << "Invalid max_protection " << info.max_protection;
  82. return false;
  83. }
  84. // Does the region fully enclose the zone pointers? Possibly unwarranted
  85. // simplification used: using the size of a full version 10 malloc zone rather
  86. // than the actual smaller size if the passed-in zone is not version 10.
  87. DCHECK(*reprotection_start <= reinterpret_cast<vm_address_t>(default_zone));
  88. vm_size_t zone_offset = reinterpret_cast<vm_address_t>(default_zone) -
  89. reinterpret_cast<vm_address_t>(*reprotection_start);
  90. DCHECK(zone_offset + sizeof(ChromeMallocZone) <= *reprotection_length);
  91. if (info.protection & VM_PROT_WRITE) {
  92. // No change needed; the zone is already writable.
  93. *reprotection_start = 0;
  94. *reprotection_length = 0;
  95. *reprotection_value = VM_PROT_NONE;
  96. } else {
  97. *reprotection_value = info.protection;
  98. result =
  99. vm_protect(mach_task_self(), *reprotection_start, *reprotection_length,
  100. false, info.protection | VM_PROT_WRITE);
  101. if (result != KERN_SUCCESS) {
  102. MACH_LOG(ERROR, result) << "vm_protect";
  103. return false;
  104. }
  105. }
  106. return true;
  107. }
  108. #if !defined(ADDRESS_SANITIZER)
  109. MallocZoneFunctions g_old_zone;
  110. MallocZoneFunctions g_old_purgeable_zone;
  111. #if !BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  112. void* oom_killer_malloc(struct _malloc_zone_t* zone, size_t size) {
  113. void* result = g_old_zone.malloc(zone, size);
  114. if (!result && size)
  115. TerminateBecauseOutOfMemory(size);
  116. return result;
  117. }
  118. void* oom_killer_calloc(struct _malloc_zone_t* zone,
  119. size_t num_items,
  120. size_t size) {
  121. void* result = g_old_zone.calloc(zone, num_items, size);
  122. if (!result && num_items && size)
  123. TerminateBecauseOutOfMemory(num_items * size);
  124. return result;
  125. }
  126. void* oom_killer_valloc(struct _malloc_zone_t* zone, size_t size) {
  127. void* result = g_old_zone.valloc(zone, size);
  128. if (!result && size)
  129. TerminateBecauseOutOfMemory(size);
  130. return result;
  131. }
  132. void oom_killer_free(struct _malloc_zone_t* zone, void* ptr) {
  133. g_old_zone.free(zone, ptr);
  134. }
  135. void* oom_killer_realloc(struct _malloc_zone_t* zone, void* ptr, size_t size) {
  136. void* result = g_old_zone.realloc(zone, ptr, size);
  137. if (!result && size)
  138. TerminateBecauseOutOfMemory(size);
  139. return result;
  140. }
  141. void* oom_killer_memalign(struct _malloc_zone_t* zone,
  142. size_t alignment,
  143. size_t size) {
  144. void* result = g_old_zone.memalign(zone, alignment, size);
  145. // Only die if posix_memalign would have returned ENOMEM, since there are
  146. // other reasons why null might be returned. See posix_memalign() in 10.15's
  147. // https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c .
  148. if (!result && size && alignment >= sizeof(void*) &&
  149. base::bits::IsPowerOfTwo(alignment)) {
  150. TerminateBecauseOutOfMemory(size);
  151. }
  152. return result;
  153. }
  154. #endif // !BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  155. void* oom_killer_malloc_purgeable(struct _malloc_zone_t* zone, size_t size) {
  156. void* result = g_old_purgeable_zone.malloc(zone, size);
  157. if (!result && size)
  158. TerminateBecauseOutOfMemory(size);
  159. return result;
  160. }
  161. void* oom_killer_calloc_purgeable(struct _malloc_zone_t* zone,
  162. size_t num_items,
  163. size_t size) {
  164. void* result = g_old_purgeable_zone.calloc(zone, num_items, size);
  165. if (!result && num_items && size)
  166. TerminateBecauseOutOfMemory(num_items * size);
  167. return result;
  168. }
  169. void* oom_killer_valloc_purgeable(struct _malloc_zone_t* zone, size_t size) {
  170. void* result = g_old_purgeable_zone.valloc(zone, size);
  171. if (!result && size)
  172. TerminateBecauseOutOfMemory(size);
  173. return result;
  174. }
  175. void oom_killer_free_purgeable(struct _malloc_zone_t* zone, void* ptr) {
  176. g_old_purgeable_zone.free(zone, ptr);
  177. }
  178. void* oom_killer_realloc_purgeable(struct _malloc_zone_t* zone,
  179. void* ptr,
  180. size_t size) {
  181. void* result = g_old_purgeable_zone.realloc(zone, ptr, size);
  182. if (!result && size)
  183. TerminateBecauseOutOfMemory(size);
  184. return result;
  185. }
  186. void* oom_killer_memalign_purgeable(struct _malloc_zone_t* zone,
  187. size_t alignment,
  188. size_t size) {
  189. void* result = g_old_purgeable_zone.memalign(zone, alignment, size);
  190. // Only die if posix_memalign would have returned ENOMEM, since there are
  191. // other reasons why null might be returned. See posix_memalign() in 10.15's
  192. // https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c .
  193. if (!result && size && alignment >= sizeof(void*) &&
  194. base::bits::IsPowerOfTwo(alignment)) {
  195. TerminateBecauseOutOfMemory(size);
  196. }
  197. return result;
  198. }
  199. #endif // !defined(ADDRESS_SANITIZER)
  200. #if !defined(ADDRESS_SANITIZER)
  201. // === Core Foundation CFAllocators ===
  202. bool CanGetContextForCFAllocator() {
  203. #if BUILDFLAG(IS_IOS)
  204. return !base::ios::IsRunningOnOrLater(17, 0, 0);
  205. #else
  206. return !base::mac::IsOSLaterThan13_DontCallThis();
  207. #endif
  208. }
  209. CFAllocatorContext* ContextForCFAllocator(CFAllocatorRef allocator) {
  210. ChromeCFAllocatorLions* our_allocator = const_cast<ChromeCFAllocatorLions*>(
  211. reinterpret_cast<const ChromeCFAllocatorLions*>(allocator));
  212. return &our_allocator->_context;
  213. }
  214. CFAllocatorAllocateCallBack g_old_cfallocator_system_default;
  215. CFAllocatorAllocateCallBack g_old_cfallocator_malloc;
  216. CFAllocatorAllocateCallBack g_old_cfallocator_malloc_zone;
  217. void* oom_killer_cfallocator_system_default(CFIndex alloc_size,
  218. CFOptionFlags hint,
  219. void* info) {
  220. void* result = g_old_cfallocator_system_default(alloc_size, hint, info);
  221. if (!result)
  222. TerminateBecauseOutOfMemory(static_cast<size_t>(alloc_size));
  223. return result;
  224. }
  225. void* oom_killer_cfallocator_malloc(CFIndex alloc_size,
  226. CFOptionFlags hint,
  227. void* info) {
  228. void* result = g_old_cfallocator_malloc(alloc_size, hint, info);
  229. if (!result)
  230. TerminateBecauseOutOfMemory(static_cast<size_t>(alloc_size));
  231. return result;
  232. }
  233. void* oom_killer_cfallocator_malloc_zone(CFIndex alloc_size,
  234. CFOptionFlags hint,
  235. void* info) {
  236. void* result = g_old_cfallocator_malloc_zone(alloc_size, hint, info);
  237. if (!result)
  238. TerminateBecauseOutOfMemory(static_cast<size_t>(alloc_size));
  239. return result;
  240. }
  241. #endif // !defined(ADDRESS_SANITIZER)
  242. // === Cocoa NSObject allocation ===
  243. typedef id (*allocWithZone_t)(id, SEL, NSZone*);
  244. allocWithZone_t g_old_allocWithZone;
  245. id oom_killer_allocWithZone(id self, SEL _cmd, NSZone* zone) {
  246. id result = g_old_allocWithZone(self, _cmd, zone);
  247. if (!result)
  248. TerminateBecauseOutOfMemory(0);
  249. return result;
  250. }
  251. void UninterceptMallocZoneForTesting(struct _malloc_zone_t* zone) {
  252. ChromeMallocZone* chrome_zone = reinterpret_cast<ChromeMallocZone*>(zone);
  253. if (!IsMallocZoneAlreadyStored(chrome_zone))
  254. return;
  255. MallocZoneFunctions& functions = GetFunctionsForZone(zone);
  256. ReplaceZoneFunctions(chrome_zone, &functions);
  257. }
  258. } // namespace
  259. bool UncheckedMallocMac(size_t size, void** result) {
  260. #if defined(ADDRESS_SANITIZER)
  261. *result = malloc(size);
  262. #else
  263. if (g_old_zone.malloc) {
  264. *result = g_old_zone.malloc(malloc_default_zone(), size);
  265. } else {
  266. *result = malloc(size);
  267. }
  268. #endif // defined(ADDRESS_SANITIZER)
  269. return *result != NULL;
  270. }
  271. bool UncheckedCallocMac(size_t num_items, size_t size, void** result) {
  272. #if defined(ADDRESS_SANITIZER)
  273. *result = calloc(num_items, size);
  274. #else
  275. if (g_old_zone.calloc) {
  276. *result = g_old_zone.calloc(malloc_default_zone(), num_items, size);
  277. } else {
  278. *result = calloc(num_items, size);
  279. }
  280. #endif // defined(ADDRESS_SANITIZER)
  281. return *result != NULL;
  282. }
  283. void InitializeDefaultDispatchToMacAllocator() {
  284. StoreFunctionsForAllZones();
  285. }
  286. void StoreFunctionsForDefaultZone() {
  287. ChromeMallocZone* default_zone = reinterpret_cast<ChromeMallocZone*>(
  288. malloc_default_zone());
  289. StoreMallocZone(default_zone);
  290. }
  291. void StoreFunctionsForAllZones() {
  292. // This ensures that the default zone is always at the front of the array,
  293. // which is important for performance.
  294. StoreFunctionsForDefaultZone();
  295. vm_address_t* zones;
  296. unsigned int count;
  297. kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
  298. if (kr != KERN_SUCCESS)
  299. return;
  300. for (unsigned int i = 0; i < count; ++i) {
  301. ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]);
  302. StoreMallocZone(zone);
  303. }
  304. }
  305. void ReplaceFunctionsForStoredZones(const MallocZoneFunctions* functions) {
  306. // The default zone does not get returned in malloc_get_all_zones().
  307. ChromeMallocZone* default_zone =
  308. reinterpret_cast<ChromeMallocZone*>(malloc_default_zone());
  309. if (DoesMallocZoneNeedReplacing(default_zone, functions)) {
  310. ReplaceZoneFunctions(default_zone, functions);
  311. }
  312. vm_address_t* zones;
  313. unsigned int count;
  314. kern_return_t kr =
  315. malloc_get_all_zones(mach_task_self(), nullptr, &zones, &count);
  316. if (kr != KERN_SUCCESS)
  317. return;
  318. for (unsigned int i = 0; i < count; ++i) {
  319. ChromeMallocZone* zone = reinterpret_cast<ChromeMallocZone*>(zones[i]);
  320. if (DoesMallocZoneNeedReplacing(zone, functions)) {
  321. ReplaceZoneFunctions(zone, functions);
  322. }
  323. }
  324. g_replaced_default_zone = true;
  325. }
  326. void InterceptAllocationsMac() {
  327. if (g_oom_killer_enabled)
  328. return;
  329. g_oom_killer_enabled = true;
  330. // === C malloc/calloc/valloc/realloc/posix_memalign ===
  331. // This approach is not perfect, as requests for amounts of memory larger than
  332. // MALLOC_ABSOLUTE_MAX_SIZE (currently SIZE_T_MAX - (2 * PAGE_SIZE)) will still
  333. // fail with a NULL rather than dying (see malloc_zone_malloc() in
  334. // https://opensource.apple.com/source/libmalloc/libmalloc-283/src/malloc.c for
  335. // details). Unfortunately, it's the best we can do. Also note that this does
  336. // not affect allocations from non-default zones.
  337. #if !defined(ADDRESS_SANITIZER)
  338. // Don't do anything special on OOM for the malloc zones replaced by
  339. // AddressSanitizer, as modifying or protecting them may not work correctly.
  340. #if !BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  341. // The malloc zone backed by PartitionAlloc crashes by default, so there is
  342. // no need to install the OOM killer.
  343. ChromeMallocZone* default_zone =
  344. reinterpret_cast<ChromeMallocZone*>(malloc_default_zone());
  345. if (!IsMallocZoneAlreadyStored(default_zone)) {
  346. StoreZoneFunctions(default_zone, &g_old_zone);
  347. MallocZoneFunctions new_functions = {};
  348. new_functions.malloc = oom_killer_malloc;
  349. new_functions.calloc = oom_killer_calloc;
  350. new_functions.valloc = oom_killer_valloc;
  351. new_functions.free = oom_killer_free;
  352. new_functions.realloc = oom_killer_realloc;
  353. new_functions.memalign = oom_killer_memalign;
  354. ReplaceZoneFunctions(default_zone, &new_functions);
  355. g_replaced_default_zone = true;
  356. }
  357. #endif // !BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC)
  358. ChromeMallocZone* purgeable_zone =
  359. reinterpret_cast<ChromeMallocZone*>(malloc_default_purgeable_zone());
  360. if (purgeable_zone && !IsMallocZoneAlreadyStored(purgeable_zone)) {
  361. StoreZoneFunctions(purgeable_zone, &g_old_purgeable_zone);
  362. MallocZoneFunctions new_functions = {};
  363. new_functions.malloc = oom_killer_malloc_purgeable;
  364. new_functions.calloc = oom_killer_calloc_purgeable;
  365. new_functions.valloc = oom_killer_valloc_purgeable;
  366. new_functions.free = oom_killer_free_purgeable;
  367. new_functions.realloc = oom_killer_realloc_purgeable;
  368. new_functions.memalign = oom_killer_memalign_purgeable;
  369. ReplaceZoneFunctions(purgeable_zone, &new_functions);
  370. }
  371. #endif
  372. // === C malloc_zone_batch_malloc ===
  373. // batch_malloc is omitted because the default malloc zone's implementation
  374. // only supports batch_malloc for "tiny" allocations from the free list. It
  375. // will fail for allocations larger than "tiny", and will only allocate as
  376. // many blocks as it's able to from the free list. These factors mean that it
  377. // can return less than the requested memory even in a non-out-of-memory
  378. // situation. There's no good way to detect whether a batch_malloc failure is
  379. // due to these other factors, or due to genuine memory or address space
  380. // exhaustion. The fact that it only allocates space from the "tiny" free list
  381. // means that it's likely that a failure will not be due to memory exhaustion.
  382. // Similarly, these constraints on batch_malloc mean that callers must always
  383. // be expecting to receive less memory than was requested, even in situations
  384. // where memory pressure is not a concern. Finally, the only public interface
  385. // to batch_malloc is malloc_zone_batch_malloc, which is specific to the
  386. // system's malloc implementation. It's unlikely that anyone's even heard of
  387. // it.
  388. #ifndef ADDRESS_SANITIZER
  389. // === Core Foundation CFAllocators ===
  390. // This will not catch allocation done by custom allocators, but will catch
  391. // all allocation done by system-provided ones.
  392. CHECK(!g_old_cfallocator_system_default && !g_old_cfallocator_malloc &&
  393. !g_old_cfallocator_malloc_zone)
  394. << "Old allocators unexpectedly non-null";
  395. bool cf_allocator_internals_known = CanGetContextForCFAllocator();
  396. if (cf_allocator_internals_known) {
  397. CFAllocatorContext* context =
  398. ContextForCFAllocator(kCFAllocatorSystemDefault);
  399. CHECK(context) << "Failed to get context for kCFAllocatorSystemDefault.";
  400. g_old_cfallocator_system_default = context->allocate;
  401. CHECK(g_old_cfallocator_system_default)
  402. << "Failed to get kCFAllocatorSystemDefault allocation function.";
  403. context->allocate = oom_killer_cfallocator_system_default;
  404. context = ContextForCFAllocator(kCFAllocatorMalloc);
  405. CHECK(context) << "Failed to get context for kCFAllocatorMalloc.";
  406. g_old_cfallocator_malloc = context->allocate;
  407. CHECK(g_old_cfallocator_malloc)
  408. << "Failed to get kCFAllocatorMalloc allocation function.";
  409. context->allocate = oom_killer_cfallocator_malloc;
  410. context = ContextForCFAllocator(kCFAllocatorMallocZone);
  411. CHECK(context) << "Failed to get context for kCFAllocatorMallocZone.";
  412. g_old_cfallocator_malloc_zone = context->allocate;
  413. CHECK(g_old_cfallocator_malloc_zone)
  414. << "Failed to get kCFAllocatorMallocZone allocation function.";
  415. context->allocate = oom_killer_cfallocator_malloc_zone;
  416. } else {
  417. DLOG(WARNING) << "Internals of CFAllocator not known; out-of-memory "
  418. "failures via CFAllocator will not result in termination. "
  419. "http://crbug.com/45650";
  420. }
  421. #endif
  422. // === Cocoa NSObject allocation ===
  423. // Note that both +[NSObject new] and +[NSObject alloc] call through to
  424. // +[NSObject allocWithZone:].
  425. CHECK(!g_old_allocWithZone) << "Old allocator unexpectedly non-null";
  426. Class nsobject_class = [NSObject class];
  427. Method orig_method =
  428. class_getClassMethod(nsobject_class, @selector(allocWithZone:));
  429. g_old_allocWithZone =
  430. reinterpret_cast<allocWithZone_t>(method_getImplementation(orig_method));
  431. CHECK(g_old_allocWithZone)
  432. << "Failed to get allocWithZone allocation function.";
  433. method_setImplementation(orig_method,
  434. reinterpret_cast<IMP>(oom_killer_allocWithZone));
  435. }
  436. void UninterceptMallocZonesForTesting() {
  437. UninterceptMallocZoneForTesting(malloc_default_zone());
  438. vm_address_t* zones;
  439. unsigned int count;
  440. kern_return_t kr = malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
  441. CHECK(kr == KERN_SUCCESS);
  442. for (unsigned int i = 0; i < count; ++i) {
  443. UninterceptMallocZoneForTesting(
  444. reinterpret_cast<struct _malloc_zone_t*>(zones[i]));
  445. }
  446. ClearAllMallocZonesForTesting();
  447. }
  448. bool AreMallocZonesIntercepted() {
  449. return !g_allocator_shims_failed_to_install;
  450. }
  451. namespace {
  452. void ShimNewMallocZonesAndReschedule(base::Time end_time,
  453. base::TimeDelta delay) {
  454. ShimNewMallocZones();
  455. if (base::Time::Now() > end_time)
  456. return;
  457. base::TimeDelta next_delay = delay * 2;
  458. SequencedTaskRunnerHandle::Get()->PostDelayedTask(
  459. FROM_HERE,
  460. base::BindOnce(&ShimNewMallocZonesAndReschedule, end_time, next_delay),
  461. delay);
  462. }
  463. } // namespace
  464. void PeriodicallyShimNewMallocZones() {
  465. base::Time end_time = base::Time::Now() + base::Minutes(1);
  466. base::TimeDelta initial_delay = base::Seconds(1);
  467. ShimNewMallocZonesAndReschedule(end_time, initial_delay);
  468. }
  469. void ShimNewMallocZones() {
  470. StoreFunctionsForAllZones();
  471. // Use the functions for the default zone as a template to replace those
  472. // new zones.
  473. ChromeMallocZone* default_zone =
  474. reinterpret_cast<ChromeMallocZone*>(malloc_default_zone());
  475. DCHECK(IsMallocZoneAlreadyStored(default_zone));
  476. MallocZoneFunctions new_functions;
  477. StoreZoneFunctions(default_zone, &new_functions);
  478. ReplaceFunctionsForStoredZones(&new_functions);
  479. }
  480. void ReplaceZoneFunctions(ChromeMallocZone* zone,
  481. const MallocZoneFunctions* functions) {
  482. // Remove protection.
  483. vm_address_t reprotection_start = 0;
  484. vm_size_t reprotection_length = 0;
  485. vm_prot_t reprotection_value = VM_PROT_NONE;
  486. bool success = DeprotectMallocZone(zone, &reprotection_start,
  487. &reprotection_length, &reprotection_value);
  488. if (!success) {
  489. g_allocator_shims_failed_to_install = true;
  490. return;
  491. }
  492. CHECK(functions->malloc && functions->calloc && functions->valloc &&
  493. functions->free && functions->realloc);
  494. zone->malloc = functions->malloc;
  495. zone->calloc = functions->calloc;
  496. zone->valloc = functions->valloc;
  497. zone->free = functions->free;
  498. zone->realloc = functions->realloc;
  499. if (functions->batch_malloc)
  500. zone->batch_malloc = functions->batch_malloc;
  501. if (functions->batch_free)
  502. zone->batch_free = functions->batch_free;
  503. if (functions->size)
  504. zone->size = functions->size;
  505. if (zone->version >= 5 && functions->memalign) {
  506. zone->memalign = functions->memalign;
  507. }
  508. if (zone->version >= 6 && functions->free_definite_size) {
  509. zone->free_definite_size = functions->free_definite_size;
  510. }
  511. // Restore protection if it was active.
  512. if (reprotection_start) {
  513. kern_return_t result =
  514. vm_protect(mach_task_self(), reprotection_start, reprotection_length,
  515. false, reprotection_value);
  516. MACH_DCHECK(result == KERN_SUCCESS, result) << "vm_protect";
  517. }
  518. }
  519. } // namespace base::allocator