Skip to content

Commit 6a89439

Browse files
authored
[sanitizer_common] Add darwin-specific MemoryRangeIsAvailable (#167797)
The fixes a TOCTOU bug in the code that initializes shadow memory in ASAN: https://github.com/llvm/llvm-project/blob/4b05581bae0e3432cfa514788418fb2fc2144904/compiler-rt/lib/asan/asan_shadow_setup.cpp#L66-L91 1. During initialization, we call `FindDynamicShadowStart` to search the memory mapping for enough space to dynamically allocate shadow memory. 2. We call `MemoryRangeIsAvailable(shadow_start, kHighShadowEnd);`, which goes into `MemoryMappingLayout`. 3. We actually map the shadow with `ReserveShadowMemoryRange`. In step 2, `MemoryMappingLayout` makes various allocations using the internal allocator. This can cause the allocator to map more memory! In some cases, this can actually allocate memory that overlaps with the shadow region returned by` FindDynamicShadowStart` in step 1. This is not actually fatal, but it memory corruption; MAP_FIXED is allowed to overlap other regions, and the effect is any overlapping memory is zeroed. ------ To address this, this PR implements `MemoryRangeIsAvailable` on Darwin without any heap allocations: - Move `IntervalsAreSeparate` into sanitizer_common.h - Guard existing sanitizer_posix implementation of `MemoryRangeIsAvailable` behind !SANITIZER_APPLE - `IsAddressInMappedRegion` in sanitizer_mac becomes `MemoryRangeIsAvailable`, which also checks for overlap with the DYLD shared cache. After this fix, it should be possible to re-land #166005, which triggered this issue on the x86 iOS simulators. rdar://164208439
1 parent c44bd37 commit 6a89439

File tree

5 files changed

+47
-20
lines changed

5 files changed

+47
-20
lines changed

compiler-rt/lib/sanitizer_common/sanitizer_common.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,13 @@ inline uptr Log2(uptr x) {
487487
return LeastSignificantSetBitIndex(x);
488488
}
489489

490+
inline bool IntervalsAreSeparate(uptr start1, uptr end1, uptr start2,
491+
uptr end2) {
492+
CHECK_LE(start1, end1);
493+
CHECK_LE(start2, end2);
494+
return (end1 < start2) || (end2 < start1);
495+
}
496+
490497
// Don't use std::min, std::max or std::swap, to minimize dependency
491498
// on libstdc++.
492499
template <class T>

compiler-rt/lib/sanitizer_common/sanitizer_mac.cpp

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ extern "C" {
103103
natural_t *nesting_depth,
104104
vm_region_recurse_info_t info,
105105
mach_msg_type_number_t *infoCnt);
106+
107+
extern const void* _dyld_get_shared_cache_range(size_t* length);
106108
}
107109

108110
# if !SANITIZER_GO
@@ -1403,15 +1405,27 @@ uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding,
14031405
return 0;
14041406
}
14051407

1406-
// Returns true if the address is definitely mapped, and false if it is not
1407-
// mapped or could not be determined.
1408-
bool IsAddressInMappedRegion(uptr addr) {
1408+
// This function (when used during initialization when there is
1409+
// only a single thread), can be used to verify that a range
1410+
// of memory hasn't already been mapped, and won't be mapped
1411+
// later in the shared cache.
1412+
//
1413+
// If the syscall mach_vm_region_recurse fails (due to sandbox),
1414+
// we assume that the memory is not mapped so that execution can continue.
1415+
//
1416+
// NOTE: range_end is inclusive
1417+
//
1418+
// WARNING: This function must NOT allocate memory, since it is
1419+
// used in InitializeShadowMemory between where we search for
1420+
// space for shadow and where we actually allocate it.
1421+
bool MemoryRangeIsAvailable(uptr range_start, uptr range_end) {
14091422
mach_vm_size_t vmsize = 0;
14101423
natural_t depth = 0;
14111424
vm_region_submap_short_info_data_64_t vminfo;
14121425
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
1413-
mach_vm_address_t address = addr;
1426+
mach_vm_address_t address = range_start;
14141427

1428+
// First, check if the range is already mapped.
14151429
kern_return_t kr =
14161430
mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth,
14171431
(vm_region_info_t)&vminfo, &count);
@@ -1423,7 +1437,24 @@ bool IsAddressInMappedRegion(uptr addr) {
14231437
Report("HINT: Is mach_vm_region_recurse allowed by sandbox?\n");
14241438
}
14251439

1426-
return (kr == KERN_SUCCESS && addr >= address && addr < address + vmsize);
1440+
if (kr == KERN_SUCCESS && !IntervalsAreSeparate(address, address + vmsize - 1,
1441+
range_start, range_end)) {
1442+
// Overlaps with already-mapped memory
1443+
return false;
1444+
}
1445+
1446+
size_t cacheLength;
1447+
uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);
1448+
1449+
if (cacheStart &&
1450+
!IntervalsAreSeparate(cacheStart, cacheStart + cacheLength - 1,
1451+
range_start, range_end)) {
1452+
// Overlaps with shared cache region
1453+
return false;
1454+
}
1455+
1456+
// We believe this address is available.
1457+
return true;
14271458
}
14281459

14291460
// FIXME implement on this platform.

compiler-rt/lib/sanitizer_common/sanitizer_mac.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@ struct ThreadEventCallbacks {
7676

7777
void InstallPthreadIntrospectionHook(const ThreadEventCallbacks &callbacks);
7878

79-
bool IsAddressInMappedRegion(uptr addr);
80-
8179
} // namespace __sanitizer
8280

8381
#endif // SANITIZER_APPLE

compiler-rt/lib/sanitizer_common/sanitizer_posix.cpp

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,9 @@ void *MapWritableFileToMemory(void *addr, uptr size, fd_t fd, OFF_T offset) {
225225
return (void *)p;
226226
}
227227

228-
static inline bool IntervalsAreSeparate(uptr start1, uptr end1,
229-
uptr start2, uptr end2) {
230-
CHECK(start1 <= end1);
231-
CHECK(start2 <= end2);
232-
return (end1 < start2) || (end2 < start1);
233-
}
234-
228+
# if !SANITIZER_APPLE
235229
// FIXME: this is thread-unsafe, but should not cause problems most of the time.
236-
// When the shadow is mapped only a single thread usually exists (plus maybe
237-
// several worker threads on Mac, which aren't expected to map big chunks of
238-
// memory).
230+
// When the shadow is mapped only a single thread usually exists
239231
bool MemoryRangeIsAvailable(uptr range_start, uptr range_end) {
240232
MemoryMappingLayout proc_maps(/*cache_enabled*/true);
241233
if (proc_maps.Error())
@@ -251,7 +243,6 @@ bool MemoryRangeIsAvailable(uptr range_start, uptr range_end) {
251243
return true;
252244
}
253245

254-
#if !SANITIZER_APPLE
255246
void DumpProcessMap() {
256247
MemoryMappingLayout proc_maps(/*cache_enabled*/true);
257248
const sptr kBufSize = 4095;
@@ -265,7 +256,7 @@ void DumpProcessMap() {
265256
Report("End of process memory map.\n");
266257
UnmapOrDie(filename, kBufSize);
267258
}
268-
#endif
259+
# endif
269260

270261
const char *GetPwd() {
271262
return GetEnv("PWD");

compiler-rt/lib/tsan/rtl/tsan_platform_mac.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ void InitializePlatformEarly() {
235235
}
236236
// In some configurations, the max_vm is expanded, but much of this space is
237237
// already mapped. TSAN will not work in this configuration.
238-
if (IsAddressInMappedRegion(HiAppMemEnd() - 1)) {
238+
if (!MemoryRangeIsAvailable(HiAppMemEnd() - 1, HiAppMemEnd())) {
239239
Report(
240240
"ThreadSanitizer: Unsupported virtual memory layout: Address %p is "
241241
"already mapped.\n",

0 commit comments

Comments
 (0)