Skip to content

Commit 479ac09

Browse files
committed
Re-exec TSan with no ASLR if memory layout is incompatible
TSan's shadow mappings only support 30-bits of ASLR entropy on x86, and it is not practical to support the maximum of 32-bits (due to pointer compression and the overhead of shadow mappings). Instead, this patch changes TSan to re-exec without ASLR if it encounters an incompatible memory layout, as suggested by Dmitry in google/sanitizers#1716. If ASLR is already disabled, it will abort. This patch involves a bit of refactoring, because the old code is: InitializePlatformEarly() InitializeAllocator() InitializePlatform(): CheckAndProtect() but it may already segfault during InitializeAllocator() if the memory layout is incompatible, before we get a chance to check in CheckAndProtect. This patch adds CheckAndProtect during InitializePlatformEarly(), before the allocator is initialized. Naturally, it is necessary to ensure that CheckAndProtect does *not* allow the heap regions to be occupied there, hence we generalize CheckAndProtect to optionally check the heap regions. We keep the original behavior of CheckAndProtect() in InitializePlatform() as a last line of defense. We need to careful not to prematurely abort if ASLR is disabled but TSan was going to re-exec for other reasons (e.g., unlimited stack size); we implement this by moving all the re-exec logic into ReExecIfNeeded().
1 parent f9da4c6 commit 479ac09

File tree

3 files changed

+136
-49
lines changed

3 files changed

+136
-49
lines changed

compiler-rt/lib/tsan/rtl/tsan_platform.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1024,7 +1024,7 @@ inline uptr RestoreAddr(uptr addr) {
10241024

10251025
void InitializePlatform();
10261026
void InitializePlatformEarly();
1027-
void CheckAndProtect();
1027+
bool CheckAndProtect(bool protect, bool ignore_heap, bool print_warnings);
10281028
void InitializeShadowMemoryPlatform();
10291029
void WriteMemoryProfile(char *buf, uptr buf_size, u64 uptime_ns);
10301030
int ExtractResolvFDs(void *state, int *fds, int nfd);

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

Lines changed: 98 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,88 @@ void InitializeShadowMemoryPlatform() {
214214

215215
#endif // #if !SANITIZER_GO
216216

217+
# if !SANITIZER_GO
218+
static void ReExecIfNeeded() {
219+
// Go maps shadow memory lazily and works fine with limited address space.
220+
// Unlimited stack is not a problem as well, because the executable
221+
// is not compiled with -pie.
222+
{
223+
bool reexec = false;
224+
// TSan doesn't play well with unlimited stack size (as stack
225+
// overlaps with shadow memory). If we detect unlimited stack size,
226+
// we re-exec the program with limited stack size as a best effort.
227+
if (StackSizeIsUnlimited()) {
228+
const uptr kMaxStackSize = 32 * 1024 * 1024;
229+
VReport(1,
230+
"Program is run with unlimited stack size, which wouldn't "
231+
"work with ThreadSanitizer.\n"
232+
"Re-execing with stack size limited to %zd bytes.\n",
233+
kMaxStackSize);
234+
SetStackSizeLimitInBytes(kMaxStackSize);
235+
reexec = true;
236+
}
237+
238+
if (!AddressSpaceIsUnlimited()) {
239+
Report(
240+
"WARNING: Program is run with limited virtual address space,"
241+
" which wouldn't work with ThreadSanitizer.\n");
242+
Report("Re-execing with unlimited virtual address space.\n");
243+
SetAddressSpaceUnlimited();
244+
reexec = true;
245+
}
246+
247+
// ASLR personality check.
248+
int old_personality = personality(0xffffffff);
249+
bool aslr_on =
250+
(old_personality != -1) && ((old_personality & ADDR_NO_RANDOMIZE) == 0);
251+
252+
# if SANITIZER_ANDROID && (defined(__aarch64__) || defined(__x86_64__))
253+
// After patch "arm64: mm: support ARCH_MMAP_RND_BITS." is introduced in
254+
// linux kernel, the random gap between stack and mapped area is increased
255+
// from 128M to 36G on 39-bit aarch64. As it is almost impossible to cover
256+
// this big range, we should disable randomized virtual space on aarch64.
257+
if (aslr_on) {
258+
VReport(1,
259+
"WARNING: Program is run with randomized virtual address "
260+
"space, which wouldn't work with ThreadSanitizer on Android.\n"
261+
"Re-execing with fixed virtual address space.\n");
262+
CHECK_NE(personality(old_personality | ADDR_NO_RANDOMIZE), -1);
263+
reexec = true;
264+
}
265+
# endif
266+
267+
if (reexec) {
268+
// Don't check the address space since we're going to re-exec anyway.
269+
} else if (!CheckAndProtect(false, false, false)) {
270+
if (aslr_on) {
271+
// Disable ASLR if the memory layout was incompatible.
272+
// Alternatively, we could just keep re-execing until we get lucky
273+
// with a compatible randomized layout, but the risk is that if it's
274+
// not an ASLR-related issue, we will be stuck in an infinite loop of
275+
// re-execing (unless we change ReExec to pass a parameter of the
276+
// number of retries allowed.)
277+
VReport(1,
278+
"WARNING: ThreadSanitizer: memory layout is incompatible, "
279+
"possibly due to high-entropy ASLR.\n"
280+
"Re-execing with fixed virtual address space.\n"
281+
"N.B. reducing ASLR entropy is preferable.\n");
282+
CHECK_NE(personality(old_personality | ADDR_NO_RANDOMIZE), -1);
283+
reexec = true;
284+
} else {
285+
VReport(1,
286+
"FATAL: ThreadSanitizer: memory layout is incompatible, "
287+
"even though ASLR is disabled.\n"
288+
"Please file a bug.\n");
289+
Die();
290+
}
291+
}
292+
293+
if (reexec)
294+
ReExec();
295+
}
296+
}
297+
# endif
298+
217299
void InitializePlatformEarly() {
218300
vmaSize =
219301
(MostSignificantSetBitIndex(GET_CURRENT_FRAME()) + 1);
@@ -284,6 +366,10 @@ void InitializePlatformEarly() {
284366
}
285367
# endif
286368
# endif
369+
370+
# if !SANITIZER_GO
371+
ReExecIfNeeded();
372+
# endif
287373
}
288374

289375
void InitializePlatform() {
@@ -294,52 +380,22 @@ void InitializePlatform() {
294380
// is not compiled with -pie.
295381
#if !SANITIZER_GO
296382
{
297-
bool reexec = false;
298-
// TSan doesn't play well with unlimited stack size (as stack
299-
// overlaps with shadow memory). If we detect unlimited stack size,
300-
// we re-exec the program with limited stack size as a best effort.
301-
if (StackSizeIsUnlimited()) {
302-
const uptr kMaxStackSize = 32 * 1024 * 1024;
303-
VReport(1, "Program is run with unlimited stack size, which wouldn't "
304-
"work with ThreadSanitizer.\n"
305-
"Re-execing with stack size limited to %zd bytes.\n",
306-
kMaxStackSize);
307-
SetStackSizeLimitInBytes(kMaxStackSize);
308-
reexec = true;
309-
}
310-
311-
if (!AddressSpaceIsUnlimited()) {
312-
Report("WARNING: Program is run with limited virtual address space,"
313-
" which wouldn't work with ThreadSanitizer.\n");
314-
Report("Re-execing with unlimited virtual address space.\n");
315-
SetAddressSpaceUnlimited();
316-
reexec = true;
317-
}
318-
#if SANITIZER_ANDROID && (defined(__aarch64__) || defined(__x86_64__))
319-
// After patch "arm64: mm: support ARCH_MMAP_RND_BITS." is introduced in
320-
// linux kernel, the random gap between stack and mapped area is increased
321-
// from 128M to 36G on 39-bit aarch64. As it is almost impossible to cover
322-
// this big range, we should disable randomized virtual space on aarch64.
323-
// ASLR personality check.
324-
int old_personality = personality(0xffffffff);
325-
if (old_personality != -1 && (old_personality & ADDR_NO_RANDOMIZE) == 0) {
326-
VReport(1, "WARNING: Program is run with randomized virtual address "
327-
"space, which wouldn't work with ThreadSanitizer.\n"
328-
"Re-execing with fixed virtual address space.\n");
329-
CHECK_NE(personality(old_personality | ADDR_NO_RANDOMIZE), -1);
330-
reexec = true;
331-
}
332-
333-
#endif
334-
#if SANITIZER_LINUX && (defined(__aarch64__) || defined(__loongarch_lp64))
383+
# if SANITIZER_LINUX && (defined(__aarch64__) || defined(__loongarch_lp64))
335384
// Initialize the xor key used in {sig}{set,long}jump.
336385
InitializeLongjmpXorKey();
337-
#endif
338-
if (reexec)
339-
ReExec();
386+
# endif
387+
}
388+
389+
// Earlier initialization steps already re-exec'ed until we got a compatible
390+
// memory layout, so we don't expect any more issues here.
391+
if (!CheckAndProtect(true, true, true)) {
392+
Printf(
393+
"FATAL: ThreadSanitizer: unexpectedly found incompatible memory "
394+
"layout.\n");
395+
Printf("FATAL: Please file a bug.\n");
396+
Die();
340397
}
341398

342-
CheckAndProtect();
343399
InitTlsSize();
344400
#endif // !SANITIZER_GO
345401
}

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

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,22 +94,51 @@ static void ProtectRange(uptr beg, uptr end) {
9494
}
9595
}
9696

97-
void CheckAndProtect() {
97+
// CheckAndProtect will check if the memory layout is compatible with TSan.
98+
// Optionally (if 'protect' is true), it will set the memory regions between
99+
// app memory to be inaccessible.
100+
// 'ignore_heap' means it will not consider heap memory allocations to be a
101+
// conflict. Set this based on whether we are calling CheckAndProtect before
102+
// or after the allocator has initialized the heap.
103+
bool CheckAndProtect(bool protect, bool ignore_heap, bool print_warnings) {
98104
// Ensure that the binary is indeed compiled with -pie.
99105
MemoryMappingLayout proc_maps(true);
100106
MemoryMappedSegment segment;
101107
while (proc_maps.Next(&segment)) {
102-
if (IsAppMem(segment.start)) continue;
108+
if (segment.start >= HeapMemBeg() && segment.end <= HeapEnd()) {
109+
if (ignore_heap) {
110+
continue;
111+
} else {
112+
return false;
113+
}
114+
}
115+
116+
// Note: IsAppMem includes if it is heap memory, hence we must
117+
// put this check after the heap bounds check.
118+
if (IsAppMem(segment.start) && IsAppMem(segment.end - 1))
119+
continue;
120+
121+
// Guard page after the heap end
103122
if (segment.start >= HeapMemEnd() && segment.start < HeapEnd()) continue;
123+
104124
if (segment.protection == 0) // Zero page or mprotected.
105125
continue;
126+
106127
if (segment.start >= VdsoBeg()) // vdso
107128
break;
108-
Printf("FATAL: ThreadSanitizer: unexpected memory mapping 0x%zx-0x%zx\n",
109-
segment.start, segment.end);
110-
Die();
129+
130+
// Debug output can break tests. Suppress this message in most cases.
131+
if (print_warnings)
132+
Printf(
133+
"WARNING: ThreadSanitizer: unexpected memory mapping 0x%zx-0x%zx\n",
134+
segment.start, segment.end);
135+
136+
return false;
111137
}
112138

139+
if (!protect)
140+
return true;
141+
113142
# if SANITIZER_IOS && !SANITIZER_IOSSIM
114143
ProtectRange(HeapMemEnd(), ShadowBeg());
115144
ProtectRange(ShadowEnd(), MetaShadowBeg());
@@ -135,8 +164,10 @@ void CheckAndProtect() {
135164
// Older s390x kernels may not support 5-level page tables.
136165
TryProtectRange(user_addr_max_l4, user_addr_max_l5);
137166
#endif
167+
168+
return true;
138169
}
139-
#endif
170+
# endif
140171

141172
} // namespace __tsan
142173

0 commit comments

Comments
 (0)