[llvm-branch-commits] [compiler-rt] [ASan][Darwin] Support gapless shadow layout for iOS 27.0 (PR #217530)

Andrew Haberlandt via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Wed Aug 19 22:13:46 PDT 2026


https://github.com/ndrewh created https://github.com/llvm/llvm-project/pull/217530

This is the second PR in a series that upstreams support for iOS 27.0 (see #217527 for the first).

When the shadow can be placed entirely above app memory (as on iOS 27), there is no need to split shadow into low/high halves with a middle gap. This PR adopts a "gapless" layout in ASAN, in which all of application memory is in `[kLowMemBeg, kLowMemEnd]` and shadow is `[kLowShadowBeg, kLowShadowEnd]`. The "high" region is unused (because of the way it is defined, `kHighMemBeg > kHighMemEnd` in this new layout, which conveniently means that `AddrIsInHighMem(p)` is always false) -- this is a bit jank and unintuitive, but it keeps the diff between iOS and other platforms relatively small.

- Add kGaplessShadow (Darwin-only) to detect this configuration.
- Teach `InitializeShadowMemory` to reserve one contiguous shadow region and protect only the shadow-of-shadow when kGaplessShadow is true
- Update PrintAddressSpaceLayout to print the single-region layout.

rdar://167657399

>From 0d30fcb0f105111c2530a91cdb1d34f9eeaf4a6d Mon Sep 17 00:00:00 2001
From: Andrew Haberlandt <ahaberlandt at apple.com>
Date: Mon, 17 Aug 2026 16:39:25 -0700
Subject: [PATCH] [ASan][Darwin] Support gapless shadow layout for iOS 27.0

When the shadow can be placed entirely above app memory (as on the
new iOS 27.0 embedded VM layout, where debug memory pushes shadow
past kHighMemEnd), there is no need to split shadow into low/high
halves with a middle gap.

- Add kGaplessShadow (Apple-only) to detect this configuration.
- Teach InitializeShadowMemory to reserve one contiguous shadow
  region and protect only the shadow-of-shadow when kGaplessShadow
  is true, with CHECKs asserting the mapping preconditions.
- Update PrintAddressSpaceLayout to print the single-region layout.

rdar://167657399
---
 compiler-rt/lib/asan/asan_mapping.h        |  9 +++
 compiler-rt/lib/asan/asan_rtl.cpp          | 76 ++++++++++++----------
 compiler-rt/lib/asan/asan_shadow_setup.cpp | 40 +++++++++++-
 3 files changed, 89 insertions(+), 36 deletions(-)

diff --git a/compiler-rt/lib/asan/asan_mapping.h b/compiler-rt/lib/asan/asan_mapping.h
index 406fcaba5692f..f514b6a4d207a 100644
--- a/compiler-rt/lib/asan/asan_mapping.h
+++ b/compiler-rt/lib/asan/asan_mapping.h
@@ -314,6 +314,15 @@ extern uptr kHighMemEnd, kMidMemBeg, kMidMemEnd;  // Initialized in __asan_init.
 #    define kMidShadowBeg MEM_TO_SHADOW(kMidMemBeg)
 #    define kMidShadowEnd MEM_TO_SHADOW(kMidMemEnd)
 
+// If the first byte of shadow can be placed after the last byte of app mem,
+// we don't need a gap since the shadow's shadow won't be in the middle
+// of app mem.
+#    if SANITIZER_APPLE
+#      define kGaplessShadow (kLowShadowBeg > kHighMemEnd)
+#    else
+#      define kGaplessShadow (false)
+#    endif
+
 // With the zero shadow base we can not actually map pages starting from 0.
 // This constant is somewhat arbitrary.
 #    define kZeroBaseShadowStart 0
diff --git a/compiler-rt/lib/asan/asan_rtl.cpp b/compiler-rt/lib/asan/asan_rtl.cpp
index c036a13a11029..2467657274a73 100644
--- a/compiler-rt/lib/asan/asan_rtl.cpp
+++ b/compiler-rt/lib/asan/asan_rtl.cpp
@@ -334,43 +334,49 @@ static void InitializeHighMemEnd() {
 }
 
 void PrintAddressSpaceLayout() {
-  if (kHighMemBeg) {
-    Printf("|| `[%p, %p]` || HighMem    ||\n",
-           (void*)kHighMemBeg, (void*)kHighMemEnd);
-    Printf("|| `[%p, %p]` || HighShadow ||\n",
-           (void*)kHighShadowBeg, (void*)kHighShadowEnd);
-  }
-  if (kMidMemBeg) {
-    Printf("|| `[%p, %p]` || ShadowGap3 ||\n",
-           (void*)kShadowGap3Beg, (void*)kShadowGap3End);
-    Printf("|| `[%p, %p]` || MidMem     ||\n",
-           (void*)kMidMemBeg, (void*)kMidMemEnd);
-    Printf("|| `[%p, %p]` || ShadowGap2 ||\n",
-           (void*)kShadowGap2Beg, (void*)kShadowGap2End);
-    Printf("|| `[%p, %p]` || MidShadow  ||\n",
-           (void*)kMidShadowBeg, (void*)kMidShadowEnd);
-  }
-  Printf("|| `[%p, %p]` || ShadowGap  ||\n",
-         (void*)kShadowGapBeg, (void*)kShadowGapEnd);
-  if (kLowShadowBeg) {
-    Printf("|| `[%p, %p]` || LowShadow  ||\n",
-           (void*)kLowShadowBeg, (void*)kLowShadowEnd);
-    Printf("|| `[%p, %p]` || LowMem     ||\n",
-           (void*)kLowMemBeg, (void*)kLowMemEnd);
-  }
-  Printf("MemToShadow(shadow): %p %p",
-         (void*)MEM_TO_SHADOW(kLowShadowBeg),
-         (void*)MEM_TO_SHADOW(kLowShadowEnd));
-  if (kHighMemBeg) {
-    Printf(" %p %p",
-           (void*)MEM_TO_SHADOW(kHighShadowBeg),
+  if (!kGaplessShadow) {
+    if (kHighMemBeg) {
+      Printf("|| `[%p, %p]` || HighMem    ||\n", (void*)kHighMemBeg,
+             (void*)kHighMemEnd);
+      Printf("|| `[%p, %p]` || HighShadow ||\n", (void*)kHighShadowBeg,
+             (void*)kHighShadowEnd);
+    }
+    if (kMidMemBeg) {
+      Printf("|| `[%p, %p]` || ShadowGap3 ||\n", (void*)kShadowGap3Beg,
+             (void*)kShadowGap3End);
+      Printf("|| `[%p, %p]` || MidMem     ||\n", (void*)kMidMemBeg,
+             (void*)kMidMemEnd);
+      Printf("|| `[%p, %p]` || ShadowGap2 ||\n", (void*)kShadowGap2Beg,
+             (void*)kShadowGap2End);
+      Printf("|| `[%p, %p]` || MidShadow  ||\n", (void*)kMidShadowBeg,
+             (void*)kMidShadowEnd);
+    }
+    Printf("|| `[%p, %p]` || ShadowGap  ||\n", (void*)kShadowGapBeg,
+           (void*)kShadowGapEnd);
+    if (kLowShadowBeg) {
+      Printf("|| `[%p, %p]` || LowShadow  ||\n", (void*)kLowShadowBeg,
+             (void*)kLowShadowEnd);
+      Printf("|| `[%p, %p]` || LowMem     ||\n", (void*)kLowMemBeg,
+             (void*)kLowMemEnd);
+    }
+    Printf("MemToShadow(shadow): %p %p", (void*)MEM_TO_SHADOW(kLowShadowBeg),
+           (void*)MEM_TO_SHADOW(kLowShadowEnd));
+    if (kHighMemBeg) {
+      Printf(" %p %p", (void*)MEM_TO_SHADOW(kHighShadowBeg),
+             (void*)MEM_TO_SHADOW(kHighShadowEnd));
+    }
+    if (kMidMemBeg) {
+      Printf(" %p %p", (void*)MEM_TO_SHADOW(kMidShadowBeg),
+             (void*)MEM_TO_SHADOW(kMidShadowEnd));
+    }
+  } else {
+    Printf("|| `[%p, %p]` || Shadow  ||\n", (void*)kLowShadowBeg,
+           (void*)kHighShadowEnd);
+    Printf("|| `[%p, %p]` || Mem     ||\n", (void*)kLowMemBeg,
+           (void*)kHighMemEnd);
+    Printf("MemToShadow(shadow): %p %p", (void*)MEM_TO_SHADOW(kLowShadowBeg),
            (void*)MEM_TO_SHADOW(kHighShadowEnd));
   }
-  if (kMidMemBeg) {
-    Printf(" %p %p",
-           (void*)MEM_TO_SHADOW(kMidShadowBeg),
-           (void*)MEM_TO_SHADOW(kMidShadowEnd));
-  }
   Printf("\n");
   Printf("redzone=%zu\n", (uptr)flags()->redzone);
   Printf("max_redzone=%zu\n", (uptr)flags()->max_redzone);
diff --git a/compiler-rt/lib/asan/asan_shadow_setup.cpp b/compiler-rt/lib/asan/asan_shadow_setup.cpp
index de3fb3910c56e..167da2c0251df 100644
--- a/compiler-rt/lib/asan/asan_shadow_setup.cpp
+++ b/compiler-rt/lib/asan/asan_shadow_setup.cpp
@@ -38,6 +38,7 @@ static void ProtectGap(uptr addr, uptr size) {
                              "unprotected gap shadow");
     return;
   }
+  VReport(2, "ProtectGap %p sz=%p\n", (void*)addr, (void*)size);
   __sanitizer::ProtectGap(addr, size, kZeroBaseShadowStart,
                           kZeroBaseMaxShadowStart);
 }
@@ -85,7 +86,44 @@ void InitializeShadowMemory() {
 
   if (Verbosity()) PrintAddressSpaceLayout();
 
-  if (full_shadow_is_available) {
+  if (full_shadow_is_available && kGaplessShadow) {
+    // Normally, the shadow memory overlaps with the memory mappable
+    // by the application, so we split shadow into "low" and "high"
+    // with a protected gap in the middle (the shadow of the shadow).
+    //
+    // However, on some platforms, we can map the shadow above
+    // the space normally addressable by the application. On these
+    // platforms, we do not need a gap.
+
+    // In the "gapless" configuration, there is only one shadow mapping
+    // which covers all app memory i.e. from kLowMemBeg to kHighMemEnd.
+    ReserveShadowMemoryRange(shadow_start, kHighShadowEnd, "shadow");
+
+    // kLowShadowEnd, kHighShadowBeg are defined assuming there is a gap,
+    // and this affects calls such as AddrIsInLowMem and AddrIsInHighMem.
+    //
+    // We want all of application memory to be in the "low mem" region and all
+    // of the shadow to be in the "low shadow" region. However, kLowMemEnd
+    // is defined differently in terms of the shadow base, which is always above
+    // the actual app mem max (i.e. >4TB, kHighMemEnd). This means
+    // (kLowMemBeg, kLowMemEnd) is a slight over-approximation of the low app
+    // memory. However, it's still good enough for us because it includes
+    // all app memory and no shadow memory, which we assert here.
+    CHECK_GE(kLowMemEnd, kHighMemEnd);
+    CHECK_LT(kLowMemEnd, kLowShadowBeg);
+    CHECK_GE(kLowShadowEnd, kHighShadowEnd);
+
+    // We don't use the "high mem" region, so we expect beg > end, to ensure
+    // that AddrIsInHighMem/AddrIsInHighShadow always fails.
+    CHECK_GT(kHighMemBeg, kHighMemEnd);
+    CHECK_GT(kHighShadowBeg, kHighShadowEnd);
+
+    // The shadow of the shadow may still technically be mappable by the
+    // sanitizers or other tools, so we protect it here just to be safe.
+    ProtectGap(
+        MEM_TO_SHADOW(kLowShadowBeg),
+        MEM_TO_SHADOW(kHighShadowEnd) - MEM_TO_SHADOW(kLowShadowBeg) + 1);
+  } else if (full_shadow_is_available) {
     // mmap the low shadow plus at least one page at the left.
     if (kLowShadowBeg)
       ReserveShadowMemoryRange(shadow_start, kLowShadowEnd, "low shadow");



More information about the llvm-branch-commits mailing list