-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[asan] Re-exec without ASLR if needed on 32-bit Linux #131975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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 llvm#78351, llvm#85142, and llvm#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.
@llvm/pr-subscribers-compiler-rt-sanitizer Author: Thurston Dang (thurstond) ChangesHigh-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:
This is loosely inspired by Full diff: https://github.com/llvm/llvm-project/pull/131975.diff 5 Files Affected:
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 <pthread.h>
# include <stdio.h>
# include <sys/mman.h>
+# include <sys/personality.h>
# include <sys/resource.h>
# include <sys/syscall.h>
# include <sys/time.h>
@@ -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() {}
|
// 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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would have
if (old_personality == -1) {
[log error]
return;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -109,6 +109,15 @@ void InitializeShadowMemory() { | |||
ProtectGap(kShadowGap2Beg, kShadowGap2End - kShadowGap2Beg + 1); | |||
ProtectGap(kShadowGap3Beg, kShadowGap3End - kShadowGap3Beg + 1); | |||
} else { | |||
# if SANITIZER_LINUX |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if the call is within this guard, why do we need a dummy for macos and windows?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
compiler-rt/lib/asan/asan_win.cpp
Outdated
@@ -43,6 +43,7 @@ uptr __asan_get_shadow_memory_dynamic_address() { | |||
__asan_init(); | |||
return __asan_shadow_memory_dynamic_address; | |||
} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
revert unrelated change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
This patch broke (at least) the Solaris/sparcv9 and Solaris/amd64 bots: Please fix. |
Fix Solaris build breakage reported in #131975 (comment)
…1975 Fix Solaris build breakage reported in #131975 (comment)
Sorry, my bad. Fixed in 89a1197c1f4f and adb57757b964 |
Fix Solaris build breakage reported in llvm/llvm-project#131975 (comment)
…inux in #131975 Fix Solaris build breakage reported in llvm/llvm-project#131975 (comment)
asan_(malloc_)?linux.cpp are misleadingly named because they cover many non-Linux OSes, such as BSDs, Fuchsia and Solaris. This is a footgun where changes may be made to these files without remembering it is not Linux-specific (e.g., I broke the Solaris build - see llvm#131975 (comment)). This patch mitigates the issue by renaming the file from ...linux to ...unix, which should hopefully give pause to anyone (me) when making Linux-specific changes.
Function definition added in #131975 was missing 'Try' (do or do not ...). My guess is buildbots mostly didn't trip because usage was gated by 'if (sizeof(uptr) == 32)', which is rare among buildbots.
This generalizes llvm#131975 to non-32-bit Linux (i.e., 64-bit Linux). This works around an edge case in 64-bit Linux, where the memory layout is incompatible if the stack size is unlimited AND ASLR entropy is 32 bits (see google/sanitizers#856 (comment)). More generally, this "re-exec if layout is incompatible" is a hammer that can work around most shadow mapping issues, without the overhead of using a dynamic shadow.
This generalizes #131975 to non-32-bit Linux (i.e., 64-bit Linux). This works around an edge case in 64-bit Linux, whereby the memory layout is incompatible if the stack size is unlimited AND ASLR entropy is 31+ bits (see google/sanitizers#856 (comment)). More generally, this "re-exec without ASLR if layout is incompatible" is a hammer that can work around most shadow mapping issues, without incurring the overhead of using a dynamic shadow.
…2682) This generalizes llvm/llvm-project#131975 to non-32-bit Linux (i.e., 64-bit Linux). This works around an edge case in 64-bit Linux, whereby the memory layout is incompatible if the stack size is unlimited AND ASLR entropy is 31+ bits (see google/sanitizers#856 (comment)). More generally, this "re-exec without ASLR if layout is incompatible" is a hammer that can work around most shadow mapping issues, without incurring the overhead of using a dynamic shadow.
// 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this correct? From what I can see, uptr is an alias of uintptr_t, meaning sizeof is 4 on 32bit systems and this branch is never taken on any platform.
(Yes, I'm late.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...oh, #132682 deleted that check. This question no longer matters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From what I can see, uptr is an alias of uintptr_t, meaning sizeof is 4 on 32bit systems and this branch is never taken on any platform.
You're right, that was a bug. Nice catch :-)
High-entropy ASLR allows up to 16-bits of entropy (2**16 4KB pages == 256MB; a bit more in practice because of implementation details), which is a significant chunk of the user address space on 32-bit systems (4GB or less). 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 changes ASan to do a one-time re-exec without ASLR if it cannot map the shadow, thus reclaiming the ~256MB of address space.
Alternatives considered:
This is loosely inspired by #78351, #85142, and #85674, though those were required because there were no static shadow mappings that could fully shadow the range of user mappings; this is not the case for ASan.