From 1c301ea1950e2de3c9c439b47f426eba3cbd8796 Mon Sep 17 00:00:00 2001 From: Andrew Haberlandt Date: Fri, 10 Oct 2025 17:52:19 -0700 Subject: [PATCH 1/5] [sanitizer-common] Improve diagnostic when ASAN fails to allocate shadow (#158378) Sometimes we are unable to find a sufficiently large gap to allocate the dynamic ASAN shadow. If a gap is not found, we will now output a (consolidated) memory map to show the user what regions were unavailable and how much memory we need. rdar://159142896 (cherry picked from commit 1fea3c507cb1ab02af5834b07a30e55c3822196c) --- .../lib/sanitizer_common/sanitizer_mac.cpp | 96 +++++++++++++++---- 1 file changed, 79 insertions(+), 17 deletions(-) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp index bb71af5ad8b6a..32544fd7562da 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp @@ -22,6 +22,11 @@ # endif # include +// Start searching for available memory region past PAGEZERO, which is +// 4KB on 32-bit and 4GB on 64-bit. +# define GAP_SEARCH_START_ADDRESS \ + ((SANITIZER_WORDSIZE == 32) ? 0x000000001000 : 0x000100000000) + # include "sanitizer_common.h" # include "sanitizer_file.h" # include "sanitizer_flags.h" @@ -58,6 +63,7 @@ extern char ***_NSGetArgv(void); # include // for dladdr() # include # include +# include # include # include # include @@ -1100,6 +1106,67 @@ static void StripEnv() { } #endif // SANITIZER_GO +// Prints out a consolidated memory map: contiguous regions +// are merged together. +static void PrintVmmap() { + const mach_vm_address_t max_vm_address = GetMaxVirtualAddress() + 1; + mach_vm_address_t address = GAP_SEARCH_START_ADDRESS; + kern_return_t kr = KERN_SUCCESS; + + Report("Memory map:\n"); + mach_vm_address_t last = 0; + mach_vm_address_t lastsz = 0; + + while (1) { + mach_vm_size_t vmsize = 0; + natural_t depth = 0; + vm_region_submap_short_info_data_64_t vminfo; + mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth, + (vm_region_info_t)&vminfo, &count); + + if (kr == KERN_DENIED) { + Report( + "ERROR: mach_vm_region_recurse got KERN_DENIED when printing memory " + "map.\n"); + Report( + "HINT: Check whether mach_vm_region_recurse is allowed by " + "sandbox.\n"); + } + + if (kr == KERN_SUCCESS && address < max_vm_address) { + if (last + lastsz == address) { + // This region is contiguous with the last; merge together. + lastsz += vmsize; + } else { + if (lastsz) + Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last, + last + lastsz, lastsz); + + last = address; + lastsz = vmsize; + } + address += vmsize; + } else { + // We've reached the end of the memory map. Print the last remaining + // region, if there is one. + if (lastsz) + Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last, + last + lastsz, lastsz); + + break; + } + } +} + +static void ReportShadowAllocFail(uptr shadow_size_bytes, uptr alignment) { + Report( + "FATAL: Failed to allocate shadow memory. Tried to allocate %p bytes " + "(alignment=%p).\n", + shadow_size_bytes, alignment); + PrintVmmap(); +} + char **GetArgv() { return *_NSGetArgv(); } @@ -1207,10 +1274,11 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale, if (new_max_vm < max_occupied_addr) { Report("Unable to find a memory range for dynamic shadow.\n"); Report( - "space_size = %p, largest_gap_found = %p, max_occupied_addr = %p, " - "new_max_vm = %p\n", - (void *)space_size, (void *)largest_gap_found, - (void *)max_occupied_addr, (void *)new_max_vm); + "\tspace_size = %p\n\tlargest_gap_found = %p\n\tmax_occupied_addr " + "= %p\n\tnew_max_vm = %p\n", + (void*)space_size, (void*)largest_gap_found, (void*)max_occupied_addr, + (void*)new_max_vm); + ReportShadowAllocFail(shadow_size_bytes, alignment); CHECK(0 && "cannot place shadow"); } RestrictMemoryToMaxAddress(new_max_vm); @@ -1221,6 +1289,7 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale, nullptr, nullptr); if (shadow_start == 0) { Report("Unable to find a memory range after restricting VM.\n"); + ReportShadowAllocFail(shadow_size_bytes, alignment); CHECK(0 && "cannot place shadow after restricting vm"); } } @@ -1236,26 +1305,19 @@ uptr MapDynamicShadowAndAliases(uptr shadow_size, uptr alias_size, } uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding, - uptr *largest_gap_found, - uptr *max_occupied_addr) { - typedef vm_region_submap_short_info_data_64_t RegionInfo; - enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 }; - // Start searching for available memory region past PAGEZERO, which is - // 4KB on 32-bit and 4GB on 64-bit. - mach_vm_address_t start_address = - (SANITIZER_WORDSIZE == 32) ? 0x000000001000 : 0x000100000000; - + uptr* largest_gap_found, + uptr* max_occupied_addr) { const mach_vm_address_t max_vm_address = GetMaxVirtualAddress() + 1; - mach_vm_address_t address = start_address; - mach_vm_address_t free_begin = start_address; + mach_vm_address_t address = GAP_SEARCH_START_ADDRESS; + mach_vm_address_t free_begin = GAP_SEARCH_START_ADDRESS; kern_return_t kr = KERN_SUCCESS; if (largest_gap_found) *largest_gap_found = 0; if (max_occupied_addr) *max_occupied_addr = 0; while (kr == KERN_SUCCESS) { mach_vm_size_t vmsize = 0; natural_t depth = 0; - RegionInfo vminfo; - mach_msg_type_number_t count = kRegionInfoSize; + vm_region_submap_short_info_data_64_t vminfo; + mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth, (vm_region_info_t)&vminfo, &count); From 28455a94c5e7bf44ff8e164638a3bfdd7ffb2f91 Mon Sep 17 00:00:00 2001 From: Andrew Haberlandt Date: Fri, 10 Oct 2025 17:52:20 -0700 Subject: [PATCH 2/5] [sanitizer_common][tsan][Darwin] Improve message for unsupported vm config on Apple platforms (#158665) An existing log message is triggered in InitializePlatformEarly if the address space max is not as sufficient for TSAN. Some Apple platforms expand the address space limit, but reserve much of the space TSAN needs. Therefore, we now check that the kernel has not mapped over the address space that we intend to use. IsAddressInMappedRegion is added to sanitizer_common. This introduces a new dependency on mach_vm_region_recurse during TSAN startup, so this intentionally fails softly (to avoid breaking current users who may be in a sandbox that doesn't allow this). rdar://135265279 (cherry picked from commit 51a840e762f506c2473f9f0cbc01b6336f99e6d1) --- .../lib/sanitizer_common/sanitizer_mac.cpp | 23 +++++++++++++++++++ .../lib/sanitizer_common/sanitizer_mac.h | 2 ++ .../lib/tsan/rtl/tsan_platform_mac.cpp | 16 ++++++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp index 32544fd7562da..9078c719be877 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp @@ -1354,6 +1354,29 @@ uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding, return 0; } +// Returns true if the address is definitely mapped, and false if it is not +// mapped or could not be determined. +bool IsAddressInMappedRegion(uptr addr) { + mach_vm_size_t vmsize = 0; + natural_t depth = 0; + vm_region_submap_short_info_data_64_t vminfo; + mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + mach_vm_address_t address = addr; + + kern_return_t kr = + mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth, + (vm_region_info_t)&vminfo, &count); + + if (kr == KERN_DENIED) { + Report( + "WARN: mach_vm_region_recurse returned KERN_DENIED when checking " + "whether an address is mapped.\n"); + Report("HINT: Is mach_vm_region_recurse allowed by sandbox?\n"); + } + + return (kr == KERN_SUCCESS && addr >= address && addr < address + vmsize); +} + // FIXME implement on this platform. void GetMemoryProfile(fill_profile_f cb, uptr *stats) {} diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.h b/compiler-rt/lib/sanitizer_common/sanitizer_mac.h index b0e4ac7f40745..789dd8e4d8e9c 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.h +++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.h @@ -76,6 +76,8 @@ struct ThreadEventCallbacks { void InstallPthreadIntrospectionHook(const ThreadEventCallbacks &callbacks); +bool IsAddressInMappedRegion(uptr addr); + } // namespace __sanitizer #endif // SANITIZER_APPLE diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp index eb344df168ab9..5cc81bab5b911 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp @@ -226,9 +226,19 @@ static void ThreadTerminateCallback(uptr thread) { void InitializePlatformEarly() { # if !SANITIZER_GO && SANITIZER_IOS uptr max_vm = GetMaxUserVirtualAddress() + 1; - if (max_vm != HiAppMemEnd()) { - Printf("ThreadSanitizer: unsupported vm address limit %p, expected %p.\n", - (void *)max_vm, (void *)HiAppMemEnd()); + if (max_vm < HiAppMemEnd()) { + Report( + "ThreadSanitizer: Unsupported virtual memory layout:\n\tVM address " + "limit = %p\n\tExpected %p.\n", + (void*)max_vm, (void*)HiAppMemEnd()); + Die(); + } + // In some configurations, the max_vm is expanded, but much of this space is + // already mapped. TSAN will not work in this configuration. + if (IsAddressInMappedRegion(HiAppMemEnd() - 1)) { + Report( + "ThreadSanitizer: Unsupported virtual memory layout: Address %p is " + "already mapped.\n"); Die(); } #endif From ead6ec0f0d488392365950027e072cb2df1cfaa3 Mon Sep 17 00:00:00 2001 From: Andrew Haberlandt Date: Fri, 10 Oct 2025 17:52:21 -0700 Subject: [PATCH 3/5] [sanitizer-common][Darwin] Improve mach_vm_region_recurse error handling (#158670) Some sanitizers use mach_vm_region_recurse on macOS to find a sufficiently large gap to allocate shadow memory. Some sandboxes do not allow this. When we get KERN_DENIED, we suggest to the user that it may have been blocked by the sandbox. For error codes other than KERN_INVALID_ADDRESS and KERN_DENIED, we make sure to log a message and not use the address. rdar://160625998 (cherry picked from commit 8fb02fae9957e828e91b4844bc7514fb319ec5d9) --- .../lib/sanitizer_common/sanitizer_mac.cpp | 33 +++++++++++++++---- .../Darwin/sandbox-vm-region-recurse.cpp | 33 +++++++++++++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 compiler-rt/test/asan/TestCases/Darwin/sandbox-vm-region-recurse.cpp diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp index 9078c719be877..ecc4569e3fa7f 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp @@ -67,6 +67,7 @@ extern char ***_NSGetArgv(void); # include # include # include +# include # include # include # include @@ -1321,17 +1322,35 @@ uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding, kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth, (vm_region_info_t)&vminfo, &count); - // There are cases where going beyond the processes' max vm does - // not return KERN_INVALID_ADDRESS so we check for going beyond that - // max address as well. - if (kr == KERN_INVALID_ADDRESS || address > max_vm_address) { + if (kr == KERN_SUCCESS) { + // There are cases where going beyond the processes' max vm does + // not return KERN_INVALID_ADDRESS so we check for going beyond that + // max address as well. + if (address > max_vm_address) { + address = max_vm_address; + kr = -1; // break after this iteration. + } + + if (max_occupied_addr) + *max_occupied_addr = address + vmsize; + } else if (kr == KERN_INVALID_ADDRESS) { // No more regions beyond "address", consider the gap at the end of VM. address = max_vm_address; - vmsize = 0; - kr = -1; // break after this iteration. + + // We will break after this iteration anyway since kr != KERN_SUCCESS + } else if (kr == KERN_DENIED) { + Report("ERROR: Unable to find a memory range for dynamic shadow.\n"); + Report("HINT: Ensure mach_vm_region_recurse is allowed under sandbox.\n"); + Die(); } else { - if (max_occupied_addr) *max_occupied_addr = address + vmsize; + Report( + "WARNING: mach_vm_region_recurse returned unexpected code %d (%s)\n", + kr, mach_error_string(kr)); + DCHECK(false && "mach_vm_region_recurse returned unexpected code"); + break; // address is not valid unless KERN_SUCCESS, therefore we must not + // use it. } + if (free_begin != address) { // We found a free region [free_begin..address-1]. uptr gap_start = RoundUpTo((uptr)free_begin + left_padding, alignment); diff --git a/compiler-rt/test/asan/TestCases/Darwin/sandbox-vm-region-recurse.cpp b/compiler-rt/test/asan/TestCases/Darwin/sandbox-vm-region-recurse.cpp new file mode 100644 index 0000000000000..c496d822a7fb8 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/Darwin/sandbox-vm-region-recurse.cpp @@ -0,0 +1,33 @@ +// Check that if mach_vm_region_recurse is disallowed by sandbox, we report a message saying so. + +// RUN: %clangxx_asan -O0 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny syscall-mig (kernel-mig-routine mach_vm_region_recurse))' %t 2>&1 | FileCheck --check-prefix=CHECK-DENY %s +// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-ALLOW %s +// RUN: %clangxx_asan -O3 %s -o %t +// RUN: not %run sandbox-exec -p '(version 1)(allow default)(deny syscall-mig (kernel-mig-routine mach_vm_region_recurse))' %t 2>&1 | FileCheck --check-prefix=CHECK-DENY %s +// RUN: not %run %t 2>&1 | FileCheck --check-prefix=CHECK-ALLOW %s + +// sandbox-exec isn't available on iOS +// UNSUPPORTED: ios + +// x86_64 does not use ASAN_SHADOW_OFFSET_DYNAMIC +// UNSUPPORTED: x86_64-darwin || x86_64h-darwin + +#include + +int main() { + char *x = (char *)malloc(10 * sizeof(char)); + free(x); + return x[5]; + // CHECK-ALLOW: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK-DENY-NOT: {{.*ERROR: AddressSanitizer: heap-use-after-free on address}} + // CHECK-ALLOW: {{READ of size 1 at 0x.* thread T0}} + // CHECK-ALLOW: {{ #0 0x.* in main}} + // CHECK-ALLOW: {{freed by thread T0 here:}} + // CHECK-ALLOW: {{ #0 0x.* in free}} + // CHECK-ALLOW: {{ #1 0x.* in main}} + // CHECK-ALLOW: {{previously allocated by thread T0 here:}} + // CHECK-ALLOW: {{ #0 0x.* in malloc}} + // CHECK-ALLOW: {{ #1 0x.* in main}} + // CHECK-DENY: {{.*HINT: Ensure mach_vm_region_recurse is allowed under sandbox}} +} From 25b64d175915d15ff367c94d0a5eb303040d817e Mon Sep 17 00:00:00 2001 From: Andrew Haberlandt Date: Fri, 10 Oct 2025 17:52:22 -0700 Subject: [PATCH 4/5] [compiler-rt] [TSan] [Darwin] Fix missing Report() arg in vm layout msg (#160171) This fixes a typo introduced in #158665. (cherry picked from commit e99c43cd13d384584357e053ab34243148ee9357) --- compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp index 5cc81bab5b911..5e1ea06b6236e 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp @@ -238,7 +238,8 @@ void InitializePlatformEarly() { if (IsAddressInMappedRegion(HiAppMemEnd() - 1)) { Report( "ThreadSanitizer: Unsupported virtual memory layout: Address %p is " - "already mapped.\n"); + "already mapped.\n", + HiAppMemEnd() - 1); Die(); } #endif From 9e97d64d689387eec541d43ea43929124e1b6578 Mon Sep 17 00:00:00 2001 From: Andrew Haberlandt Date: Fri, 10 Oct 2025 17:52:23 -0700 Subject: [PATCH 5/5] [sanitizers][Darwin][NFC] Insert missing void* casts for printf %p (#161282) Add some missing void* casts where we were passing an int for %p on some platforms. rdar://161174839 (cherry picked from commit 5960f5cf550cfd5376a18d0f2c551422d302f919) --- compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp | 10 +++++----- compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp index ecc4569e3fa7f..a9ede5d2323ed 100644 --- a/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp +++ b/compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp @@ -1141,8 +1141,8 @@ static void PrintVmmap() { lastsz += vmsize; } else { if (lastsz) - Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last, - last + lastsz, lastsz); + Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", (void*)last, + (void*)(last + lastsz), lastsz); last = address; lastsz = vmsize; @@ -1152,8 +1152,8 @@ static void PrintVmmap() { // We've reached the end of the memory map. Print the last remaining // region, if there is one. if (lastsz) - Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last, - last + lastsz, lastsz); + Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", (void*)last, + (void*)(last + lastsz), lastsz); break; } @@ -1164,7 +1164,7 @@ static void ReportShadowAllocFail(uptr shadow_size_bytes, uptr alignment) { Report( "FATAL: Failed to allocate shadow memory. Tried to allocate %p bytes " "(alignment=%p).\n", - shadow_size_bytes, alignment); + (void*)shadow_size_bytes, (void*)alignment); PrintVmmap(); } diff --git a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp index 5e1ea06b6236e..62ab0554df08e 100644 --- a/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp +++ b/compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp @@ -239,7 +239,7 @@ void InitializePlatformEarly() { Report( "ThreadSanitizer: Unsupported virtual memory layout: Address %p is " "already mapped.\n", - HiAppMemEnd() - 1); + (void*)(HiAppMemEnd() - 1)); Die(); } #endif