[asan] Re-exec without ASLR if needed on 32-bit Linux (#131975)

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:
1) We don't 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 for 32-bit
Linux relies on the compile-time 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 shadow mappings that could fully
shadow the range of user mappings; this is not the case for ASan.
This commit is contained in:
Thurston Dang 2025-03-20 08:02:48 -07:00 committed by GitHub
parent fdeb2ff304
commit 3b3f8c50b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 46 additions and 0 deletions

View File

@ -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.

View File

@ -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,37 @@ 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);
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.
// 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() {}

View File

@ -55,6 +55,9 @@ uptr FindDynamicShadowStart() {
GetMmapGranularity());
}
// Not used.
void TryReExecWithoutASLR() {}
// No-op. Mac does not support static linkage anyway.
void AsanCheckDynamicRTPrereqs() {}

View File

@ -109,6 +109,13 @@ void InitializeShadowMemory() {
ProtectGap(kShadowGap2Beg, kShadowGap2End - kShadowGap2Beg + 1);
ProtectGap(kShadowGap3Beg, kShadowGap3End - kShadowGap3Beg + 1);
} else {
// 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();
Report(
"Shadow memory range interleaves with an existing memory mapping. "
"ASan cannot proceed correctly. ABORTING.\n");

View File

@ -279,6 +279,9 @@ uptr FindDynamicShadowStart() {
GetMmapGranularity());
}
// Not used
void TryReExecWithoutASLR() {}
void AsanCheckDynamicRTPrereqs() {}
void AsanCheckIncompatibleRT() {}