Skip to content

Commit 35dc944

Browse files
committed
Reapply "[asan] Fix misalignment of variables in fake stack frames" (llvm#153139)
This reverts commit 29ad073 i.e., relands 927e19f. It was reverted because of buildbot breakages. This reland adds "-pthread" and also moves the test to Posix-only. Original commit message: [asan] Fix misalignment of variables in fake stack frames (llvm#152819) ASan's instrumentation pass uses `ASanStackFrameLayout::ComputeASanStackFrameLayout()` to calculate the offset of variables, taking into account alignment. However, the fake stack frames returned by the runtime's `GetFrame()` are not guaranteed to be sufficiently aligned (and in some cases, even guaranteed to be misaligned), hence the offset addresses may sometimes be misaligned. This change fixes the misalignment issue by padding the FakeStack. Every fake stack frame is guaranteed to be aligned to the size of the frame. The memory overhead is low: 64KB per FakeStack, compared to the FakeStack size of ~700KB (min) to 11MB (max). Updates the test case from llvm#152889.
1 parent 29ad073 commit 35dc944

File tree

4 files changed

+61
-16
lines changed

4 files changed

+61
-16
lines changed

compiler-rt/lib/asan/asan_fake_stack.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,34 @@ FakeStack *FakeStack::Create(uptr stack_size_log) {
5454
stack_size_log = kMinStackSizeLog;
5555
if (stack_size_log > kMaxStackSizeLog)
5656
stack_size_log = kMaxStackSizeLog;
57+
CHECK_LE(kMaxStackFrameSizeLog, stack_size_log);
5758
uptr size = RequiredSize(stack_size_log);
59+
uptr padded_size = size + kMaxStackFrameSize;
60+
void *true_res = reinterpret_cast<void *>(
61+
flags()->uar_noreserve ? MmapNoReserveOrDie(padded_size, "FakeStack")
62+
: MmapOrDie(padded_size, "FakeStack"));
63+
// GetFrame() requires the property that
64+
// (res + kFlagsOffset + SizeRequiredForFlags(stack_size_log)) is aligned to
65+
// kMaxStackFrameSize.
66+
// We didn't use MmapAlignedOrDieOnFatalError, because it requires that the
67+
// *size* is a power of 2, which is an overly strong condition.
68+
static_assert(alignof(FakeStack) <= kMaxStackFrameSize);
5869
FakeStack *res = reinterpret_cast<FakeStack *>(
59-
flags()->uar_noreserve ? MmapNoReserveOrDie(size, "FakeStack")
60-
: MmapOrDie(size, "FakeStack"));
70+
RoundUpTo(
71+
(uptr)true_res + kFlagsOffset + SizeRequiredForFlags(stack_size_log),
72+
kMaxStackFrameSize) -
73+
kFlagsOffset - SizeRequiredForFlags(stack_size_log));
74+
res->true_start = true_res;
6175
res->stack_size_log_ = stack_size_log;
6276
u8 *p = reinterpret_cast<u8 *>(res);
6377
VReport(1,
6478
"T%d: FakeStack created: %p -- %p stack_size_log: %zd; "
65-
"mmapped %zdK, noreserve=%d \n",
79+
"mmapped %zdK, noreserve=%d, true_start: %p, start of first frame: "
80+
"0x%zx\n",
6681
GetCurrentTidOrInvalid(), (void *)p,
6782
(void *)(p + FakeStack::RequiredSize(stack_size_log)), stack_size_log,
68-
size >> 10, flags()->uar_noreserve);
83+
size >> 10, flags()->uar_noreserve, res->true_start,
84+
res->GetFrame(stack_size_log, /*class_id*/ 0, /*pos*/ 0));
6985
return res;
7086
}
7187

@@ -79,8 +95,10 @@ void FakeStack::Destroy(int tid) {
7995
Report("T%d: FakeStack destroyed: %s\n", tid, str.data());
8096
}
8197
uptr size = RequiredSize(stack_size_log_);
82-
FlushUnneededASanShadowMemory(reinterpret_cast<uptr>(this), size);
83-
UnmapOrDie(this, size);
98+
uptr padded_size = size + kMaxStackFrameSize;
99+
FlushUnneededASanShadowMemory(reinterpret_cast<uptr>(true_start),
100+
padded_size);
101+
UnmapOrDie(true_start, padded_size);
84102
}
85103

86104
void FakeStack::PoisonAll(u8 magic) {

compiler-rt/lib/asan/asan_fake_stack.h

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ struct FakeFrame {
3232
// is not popped but remains there for quite some time until gets used again.
3333
// So, we poison the objects on the fake stack when function returns.
3434
// It helps us find use-after-return bugs.
35-
//
3635
// The FakeStack objects is allocated by a single mmap call and has no other
3736
// pointers. The size of the fake stack depends on the actual thread stack size
3837
// and thus can not be a constant.
3938
// stack_size is a power of two greater or equal to the thread's stack size;
4039
// we store it as its logarithm (stack_size_log).
40+
// FakeStack is padded such that GetFrame() is aligned to BytesInSizeClass().
4141
// FakeStack has kNumberOfSizeClasses (11) size classes, each size class
4242
// is a power of two, starting from 64 bytes. Each size class occupies
4343
// stack_size bytes and thus can allocate
@@ -56,6 +56,9 @@ struct FakeFrame {
5656
class FakeStack {
5757
static const uptr kMinStackFrameSizeLog = 6; // Min frame is 64B.
5858
static const uptr kMaxStackFrameSizeLog = 16; // Max stack frame is 64K.
59+
static_assert(kMaxStackFrameSizeLog >= kMinStackFrameSizeLog);
60+
61+
static const u64 kMaxStackFrameSize = 1 << kMaxStackFrameSizeLog;
5962

6063
public:
6164
static const uptr kNumberOfSizeClasses =
@@ -66,7 +69,7 @@ class FakeStack {
6669

6770
void Destroy(int tid);
6871

69-
// stack_size_log is at least 15 (stack_size >= 32K).
72+
// min_uar_stack_size_log is 16 (stack_size >= 64KB)
7073
static uptr SizeRequiredForFlags(uptr stack_size_log) {
7174
return ((uptr)1) << (stack_size_log + 1 - kMinStackFrameSizeLog);
7275
}
@@ -110,6 +113,28 @@ class FakeStack {
110113
}
111114

112115
// Get frame by class_id and pos.
116+
// Return values are guaranteed to be aligned to BytesInSizeClass(class_id),
117+
// which is useful in combination with
118+
// ASanStackFrameLayout::ComputeASanStackFrameLayout().
119+
//
120+
// Note that alignment to 1<<kMaxStackFrameSizeLog (aka
121+
// BytesInSizeClass(max_class_id)) implies alignment to BytesInSizeClass()
122+
// for any class_id, since the class sizes are increasing powers of 2.
123+
//
124+
// 1) (this + kFlagsOffset + SizeRequiredForFlags())) is aligned to
125+
// 1<<kMaxStackFrameSizeLog (see FakeStack::Create)
126+
//
127+
// Note that SizeRequiredForFlags(16) == 2048. If FakeStack::Create() had
128+
// merely returned an address from mmap (4K-aligned), the addition would
129+
// not be 4K-aligned.
130+
// 2) We know that stack_size_log >= kMaxStackFrameSizeLog (otherwise you
131+
// couldn't store a single frame of that size in the entire stack)
132+
// hence (1<<stack_size_log) is aligned to 1<<kMaxStackFrameSizeLog
133+
// and ((1<<stack_size_log) * class_id) is aligned to
134+
// 1<<kMaxStackFrameSizeLog
135+
// 3) BytesInSizeClass(class_id) * pos is aligned to
136+
// BytesInSizeClass(class_id)
137+
// The sum of these is aligned to BytesInSizeClass(class_id).
113138
u8 *GetFrame(uptr stack_size_log, uptr class_id, uptr pos) {
114139
return reinterpret_cast<u8 *>(this) + kFlagsOffset +
115140
SizeRequiredForFlags(stack_size_log) +
@@ -156,15 +181,18 @@ class FakeStack {
156181

157182
private:
158183
FakeStack() { }
159-
static const uptr kFlagsOffset = 4096; // This is were the flags begin.
184+
static const uptr kFlagsOffset = 4096; // This is where the flags begin.
160185
// Must match the number of uses of DEFINE_STACK_MALLOC_FREE_WITH_CLASS_ID
161186
COMPILER_CHECK(kNumberOfSizeClasses == 11);
162187
static const uptr kMaxStackMallocSize = ((uptr)1) << kMaxStackFrameSizeLog;
163188

164189
uptr hint_position_[kNumberOfSizeClasses];
165190
uptr stack_size_log_;
166-
// a bit is set if something was allocated from the corresponding size class.
167191
bool needs_gc_;
192+
// We allocated more memory than needed to ensure the FakeStack (and, by
193+
// extension, each of the fake stack frames) is aligned. We keep track of the
194+
// true start so that we can unmap it.
195+
void *true_start;
168196
};
169197

170198
FakeStack *GetTLSFakeStack();

compiler-rt/lib/asan/tests/asan_fake_stack_test.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ TEST(FakeStack, Allocate) {
113113
uptr bytes_in_class = FakeStack::BytesInSizeClass(cid);
114114
for (uptr j = 0; j < n; j++) {
115115
FakeFrame *ff = fs->Allocate(stack_size_log, cid, 0);
116+
EXPECT_EQ(reinterpret_cast<uptr>(ff) % bytes_in_class, 0U);
116117
uptr x = reinterpret_cast<uptr>(ff);
117118
EXPECT_TRUE(s.insert(std::make_pair(ff, cid)).second);
118119
EXPECT_EQ(x, fs->AddrIsInFakeStack(x));

compiler-rt/test/asan/TestCases/fakestack_alignment.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
// Regression test 1:
2-
// This deterministically fails: when the stack size is 1<<16, FakeStack's
3-
// GetFrame() is out of alignment, because SizeRequiredForFlags(16) == 2K.
2+
// When the stack size is 1<<16, SizeRequiredForFlags(16) == 2KB. This forces
3+
// FakeStack's GetFrame() out of alignment if the FakeStack isn't padded.
44
// RUN: %clangxx_asan -fsanitize-address-use-after-return=always -O0 -DALIGNMENT=4096 -DTHREAD_COUNT=1 -DTHREAD_STACK_SIZE=65536 %s -o %t && %run %t 2>&1
55

66
// Regression test 2:
7-
// The FakeStack frame is not guaranteed to be aligned, but alignment can
8-
// happen by chance, so try this on many threads.
7+
// Check that the FakeStack frame is aligned, beyond the typical 4KB page
8+
// alignment. Alignment can happen by chance, so try this on many threads.
99
// RUN: %clangxx_asan -fsanitize-address-use-after-return=always -O0 -DALIGNMENT=8192 -DTHREAD_COUNT=32 -DTHREAD_STACK_SIZE=131072 %s -o %t && %run %t 2>&1
1010
// RUN: %clangxx_asan -fsanitize-address-use-after-return=always -O0 -DALIGNMENT=16384 -DTHREAD_COUNT=32 -DTHREAD_STACK_SIZE=131072 %s -o %t && %run %t 2>&1
1111

@@ -17,8 +17,6 @@
1717
// RUN: %clangxx_asan -fsanitize-address-use-after-return=always -O0 -DALIGNMENT=8192 -DTHREAD_COUNT=32 -DTHREAD_STACK_SIZE=131072 %s -o %t && %run %t 2>&1
1818
// RUN: %clangxx_asan -fsanitize-address-use-after-return=always -O0 -DALIGNMENT=16384 -DTHREAD_COUNT=32 -DTHREAD_STACK_SIZE=131072 %s -o %t && %run %t 2>&1
1919

20-
// XFAIL: *
21-
2220
#include <assert.h>
2321
#include <pthread.h>
2422
#include <stdio.h>

0 commit comments

Comments
 (0)