From 8b2e5e92a3bdb3105f1326d7a273321c49b5794d Mon Sep 17 00:00:00 2001 From: Thurston Dang Date: Wed, 19 Mar 2025 05:34:55 +0000 Subject: [PATCH 1/3] [asan] Re-exec without ASLR if needed on 32-bit Linux High-entropy ASLR allows up to 16-bits of entropy (256MB), which is a significant chunk of the 32-bit address space (4GB, less if running with a 32-bit kernel). This, combined with ASan's shadow (512MB) and ASan's fixed shadow offset (512MB), makes it possible for large binaries to fail to map the shadow. This patch will re-exec without ASLR if it cannot map the shadow, thus reclaiming the 256MB of address space. Alternatives considered: 1) We don't attempt to lower ASan's fixed shadow offset, because that would limit non-PIE binaries. 2) We don't switch to a dynamic shadow offset, because ASan on 32-bit Linux relies on the constant offset to optimize its instrumentation and compiler-rt. This is loosely inspired by https://github.com/llvm/llvm-project/pull/78351, https://github.com/llvm/llvm-project/pull/85142, and https://github.com/llvm/llvm-project/pull/85674, though those were required because there were no static mappings that could fully shadow the range of user mappings; this is not the case for ASan. --- compiler-rt/lib/asan/asan_internal.h | 1 + compiler-rt/lib/asan/asan_linux.cpp | 28 ++++++++++++++++++++++ compiler-rt/lib/asan/asan_mac.cpp | 3 +++ compiler-rt/lib/asan/asan_shadow_setup.cpp | 9 +++++++ compiler-rt/lib/asan/asan_win.cpp | 4 ++++ 5 files changed, 45 insertions(+) diff --git a/compiler-rt/lib/asan/asan_internal.h b/compiler-rt/lib/asan/asan_internal.h index 06dfc4b177339..8205e19fdab7f 100644 --- a/compiler-rt/lib/asan/asan_internal.h +++ b/compiler-rt/lib/asan/asan_internal.h @@ -82,6 +82,7 @@ void ReplaceSystemMalloc(); uptr FindDynamicShadowStart(); void AsanCheckDynamicRTPrereqs(); void AsanCheckIncompatibleRT(); +void TryReExecWithoutASLR(); // Unpoisons platform-specific stacks. // Returns true if all stacks have been unpoisoned. diff --git a/compiler-rt/lib/asan/asan_linux.cpp b/compiler-rt/lib/asan/asan_linux.cpp index 4cabca388ca9a..7f502df3cf1b4 100644 --- a/compiler-rt/lib/asan/asan_linux.cpp +++ b/compiler-rt/lib/asan/asan_linux.cpp @@ -21,6 +21,7 @@ # include # include # include +# include # include # include # include @@ -107,6 +108,33 @@ void FlushUnneededASanShadowMemory(uptr p, uptr size) { ReleaseMemoryPagesToOS(MemToShadow(p), MemToShadow(p + size)); } +void ReExecWithoutASLR() { + // ASLR personality check. + // Caution: 'personality' is sometimes forbidden by sandboxes, so only call + // this function as a last resort (when the memory mapping is incompatible + // and ASan would fail anyway). + int old_personality = personality(0xffffffff); + bool aslr_on = + (old_personality != -1) && ((old_personality & ADDR_NO_RANDOMIZE) == 0); + + if (aslr_on) { + // Disable ASLR if the memory layout was incompatible. + // Alternatively, we could just keep re-execing until we get lucky + // with a compatible randomized layout, but the risk is that if it's + // not an ASLR-related issue, we will be stuck in an infinite loop of + // re-execing (unless we change ReExec to pass a parameter of the + // number of retries allowed.) + VReport(1, + "WARNING: AddressSanitizer: memory layout is incompatible, " + "possibly due to high-entropy ASLR.\n" + "Re-execing with fixed virtual address space.\n" + "N.B. reducing ASLR entropy is preferable.\n"); + CHECK_NE(personality(old_personality | ADDR_NO_RANDOMIZE), -1); + + ReExec(); + } +} + # if SANITIZER_ANDROID // FIXME: should we do anything for Android? void AsanCheckDynamicRTPrereqs() {} diff --git a/compiler-rt/lib/asan/asan_mac.cpp b/compiler-rt/lib/asan/asan_mac.cpp index bfc349223258b..be513a03ed5cd 100644 --- a/compiler-rt/lib/asan/asan_mac.cpp +++ b/compiler-rt/lib/asan/asan_mac.cpp @@ -55,6 +55,9 @@ uptr FindDynamicShadowStart() { GetMmapGranularity()); } +// Not used. +void TryReExecWithoutASLR() {} + // No-op. Mac does not support static linkage anyway. void AsanCheckDynamicRTPrereqs() {} diff --git a/compiler-rt/lib/asan/asan_shadow_setup.cpp b/compiler-rt/lib/asan/asan_shadow_setup.cpp index fc6de39622b51..ad24f63985f8a 100644 --- a/compiler-rt/lib/asan/asan_shadow_setup.cpp +++ b/compiler-rt/lib/asan/asan_shadow_setup.cpp @@ -109,6 +109,15 @@ void InitializeShadowMemory() { ProtectGap(kShadowGap2Beg, kShadowGap2End - kShadowGap2Beg + 1); ProtectGap(kShadowGap3Beg, kShadowGap3End - kShadowGap3Beg + 1); } else { +# if SANITIZER_LINUX + // The shadow mappings can shadow the entire user address space. However, + // on 32-bit systems, the maximum ASLR entropy (currently up to 16-bits + // == 256MB) is a significant chunk of the address space; reclaiming it by + // disabling ASLR might allow chonky binaries to run. + if (sizeof(uptr) == 32) + TryReExecWithoutASLR(); +# endif + Report( "Shadow memory range interleaves with an existing memory mapping. " "ASan cannot proceed correctly. ABORTING.\n"); diff --git a/compiler-rt/lib/asan/asan_win.cpp b/compiler-rt/lib/asan/asan_win.cpp index 027340280e068..7e6792c78d5e6 100644 --- a/compiler-rt/lib/asan/asan_win.cpp +++ b/compiler-rt/lib/asan/asan_win.cpp @@ -43,6 +43,7 @@ uptr __asan_get_shadow_memory_dynamic_address() { __asan_init(); return __asan_shadow_memory_dynamic_address; } + } // extern "C" // ---------------------- Windows-specific interceptors ---------------- {{{ @@ -279,6 +280,9 @@ uptr FindDynamicShadowStart() { GetMmapGranularity()); } +// Not used +void TryReExecWithoutASLR() {} + void AsanCheckDynamicRTPrereqs() {} void AsanCheckIncompatibleRT() {} From 4e278fc8fbff7a0e9cd4e30fb49120a1e561fffc Mon Sep 17 00:00:00 2001 From: Thurston Dang Date: Wed, 19 Mar 2025 20:46:12 +0000 Subject: [PATCH 2/3] Florian's feedback --- compiler-rt/lib/asan/asan_linux.cpp | 8 ++++++-- compiler-rt/lib/asan/asan_shadow_setup.cpp | 2 -- compiler-rt/lib/asan/asan_win.cpp | 1 - 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler-rt/lib/asan/asan_linux.cpp b/compiler-rt/lib/asan/asan_linux.cpp index 7f502df3cf1b4..fbc7a172335d2 100644 --- a/compiler-rt/lib/asan/asan_linux.cpp +++ b/compiler-rt/lib/asan/asan_linux.cpp @@ -114,8 +114,12 @@ void ReExecWithoutASLR() { // this function as a last resort (when the memory mapping is incompatible // and ASan would fail anyway). int old_personality = personality(0xffffffff); - bool aslr_on = - (old_personality != -1) && ((old_personality & ADDR_NO_RANDOMIZE) == 0); + if (old_personality == -1) { + VReport(1, "WARNING: unable to run personality check.\n"); + return; + } + + bool aslr_on = ((old_personality & ADDR_NO_RANDOMIZE) == 0); if (aslr_on) { // Disable ASLR if the memory layout was incompatible. diff --git a/compiler-rt/lib/asan/asan_shadow_setup.cpp b/compiler-rt/lib/asan/asan_shadow_setup.cpp index ad24f63985f8a..e66b8af1d2c30 100644 --- a/compiler-rt/lib/asan/asan_shadow_setup.cpp +++ b/compiler-rt/lib/asan/asan_shadow_setup.cpp @@ -109,14 +109,12 @@ void InitializeShadowMemory() { ProtectGap(kShadowGap2Beg, kShadowGap2End - kShadowGap2Beg + 1); ProtectGap(kShadowGap3Beg, kShadowGap3End - kShadowGap3Beg + 1); } else { -# if SANITIZER_LINUX // The shadow mappings can shadow the entire user address space. However, // on 32-bit systems, the maximum ASLR entropy (currently up to 16-bits // == 256MB) is a significant chunk of the address space; reclaiming it by // disabling ASLR might allow chonky binaries to run. if (sizeof(uptr) == 32) TryReExecWithoutASLR(); -# endif Report( "Shadow memory range interleaves with an existing memory mapping. " diff --git a/compiler-rt/lib/asan/asan_win.cpp b/compiler-rt/lib/asan/asan_win.cpp index 7e6792c78d5e6..845408ac38abc 100644 --- a/compiler-rt/lib/asan/asan_win.cpp +++ b/compiler-rt/lib/asan/asan_win.cpp @@ -43,7 +43,6 @@ uptr __asan_get_shadow_memory_dynamic_address() { __asan_init(); return __asan_shadow_memory_dynamic_address; } - } // extern "C" // ---------------------- Windows-specific interceptors ---------------- {{{ From f1eea40972b31ae5dc2d7eec6dfd9ba4c6146e87 Mon Sep 17 00:00:00 2001 From: Thurston Dang Date: Thu, 20 Mar 2025 02:30:13 +0000 Subject: [PATCH 3/3] The anti-Lisp conspiracy --- compiler-rt/lib/asan/asan_linux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler-rt/lib/asan/asan_linux.cpp b/compiler-rt/lib/asan/asan_linux.cpp index fbc7a172335d2..7620ecb1e81f9 100644 --- a/compiler-rt/lib/asan/asan_linux.cpp +++ b/compiler-rt/lib/asan/asan_linux.cpp @@ -119,7 +119,7 @@ void ReExecWithoutASLR() { return; } - bool aslr_on = ((old_personality & ADDR_NO_RANDOMIZE) == 0); + bool aslr_on = (old_personality & ADDR_NO_RANDOMIZE) == 0; if (aslr_on) { // Disable ASLR if the memory layout was incompatible.