Skip to content

Commit 05d1a2b

Browse files
committed
[GWP-ASan] Guard against recursive allocs. Pack TLS for perf.
Summary: Add a recursivity guard for GPA::allocate(). This means that any recursive allocations will fall back to the supporting allocator. In future patches, we will introduce stack trace collection support. The unwinder will be provided by the supporting allocator, and we can't guarantee they don't call malloc() (e.g. backtrace() on posix may call dlopen(), which may call malloc(). Furthermore, this patch packs the new TLS recursivity guard into a thread local struct, so that TLS variables should be hopefully not fall across cache lines. Reviewers: vlad.tsyrklevich, morehouse, eugenis Reviewed By: eugenis Subscribers: kubamracek, #sanitizers, llvm-commits, eugenis Tags: #sanitizers, #llvm Differential Revision: https://reviews.llvm.org/D63736 llvm-svn: 364356
1 parent 9c10b62 commit 05d1a2b

File tree

2 files changed

+51
-15
lines changed

2 files changed

+51
-15
lines changed

compiler-rt/lib/gwp_asan/guarded_pool_allocator.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ void GuardedPoolAllocator::AllocationMetadata::RecordAllocation(
4949

5050
void GuardedPoolAllocator::AllocationMetadata::RecordDeallocation() {
5151
IsDeallocated = true;
52-
// TODO(hctim): Implement stack trace collection.
52+
// TODO(hctim): Implement stack trace collection. Ensure that the unwinder is
53+
// not called if we have our recursive flag called, otherwise non-reentrant
54+
// unwinders may deadlock.
5355
DeallocationTrace.ThreadID = getThreadID();
5456
}
5557

@@ -124,7 +126,28 @@ void GuardedPoolAllocator::init(const options::Options &Opts) {
124126
installSignalHandlers();
125127
}
126128

129+
namespace {
130+
class ScopedBoolean {
131+
public:
132+
ScopedBoolean(bool &B) : Bool(B) { Bool = true; }
133+
~ScopedBoolean() { Bool = false; }
134+
135+
private:
136+
bool &Bool;
137+
};
138+
} // anonymous namespace
139+
127140
void *GuardedPoolAllocator::allocate(size_t Size) {
141+
// GuardedPagePoolEnd == 0 when GWP-ASan is disabled. If we are disabled, fall
142+
// back to the supporting allocator.
143+
if (GuardedPagePoolEnd == 0)
144+
return nullptr;
145+
146+
// Protect against recursivity.
147+
if (ThreadLocals.RecursiveGuard)
148+
return nullptr;
149+
ScopedBoolean SB(ThreadLocals.RecursiveGuard);
150+
128151
if (Size == 0 || Size > maximumAllocationSize())
129152
return nullptr;
130153

@@ -385,8 +408,7 @@ struct ScopedEndOfReportDecorator {
385408
options::Printf_t Printf;
386409
};
387410

388-
void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr,
389-
Error E) {
411+
void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr, Error E) {
390412
if (!pointerIsMine(reinterpret_cast<void *>(AccessPtr))) {
391413
return;
392414
}
@@ -395,6 +417,7 @@ void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr,
395417
// This does not guarantee that there are no races, because another thread can
396418
// take the locks during the time that the signal handler is being called.
397419
PoolMutex.tryLock();
420+
ThreadLocals.RecursiveGuard = true;
398421

399422
Printf("*** GWP-ASan detected a memory error ***\n");
400423
ScopedEndOfReportDecorator Decorator(Printf);
@@ -429,5 +452,7 @@ void GuardedPoolAllocator::reportErrorInternal(uintptr_t AccessPtr,
429452
// TODO(hctim): Implement dumping here of allocation/deallocation traces.
430453
}
431454

432-
TLS_INITIAL_EXEC uint64_t GuardedPoolAllocator::NextSampleCounter = 0;
455+
TLS_INITIAL_EXEC
456+
GuardedPoolAllocator::ThreadLocalPackedVariables
457+
GuardedPoolAllocator::ThreadLocals;
433458
} // namespace gwp_asan

compiler-rt/lib/gwp_asan/guarded_pool_allocator.h

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,11 @@ class GuardedPoolAllocator {
9696
ALWAYS_INLINE bool shouldSample() {
9797
// NextSampleCounter == 0 means we "should regenerate the counter".
9898
// == 1 means we "should sample this allocation".
99-
if (UNLIKELY(NextSampleCounter == 0)) {
100-
// GuardedPagePoolEnd == 0 if GWP-ASan is disabled.
101-
if (UNLIKELY(GuardedPagePoolEnd == 0))
102-
return false;
103-
NextSampleCounter = (getRandomUnsigned32() % AdjustedSampleRate) + 1;
104-
}
105-
106-
return UNLIKELY(--NextSampleCounter == 0);
99+
if (UNLIKELY(ThreadLocals.NextSampleCounter == 0))
100+
ThreadLocals.NextSampleCounter =
101+
(getRandomUnsigned32() % AdjustedSampleRate) + 1;
102+
103+
return UNLIKELY(--ThreadLocals.NextSampleCounter == 0);
107104
}
108105

109106
// Returns whether the provided pointer is a current sampled allocation that
@@ -245,9 +242,23 @@ class GuardedPoolAllocator {
245242
// GWP-ASan is disabled, we wish to never spend wasted cycles recalculating
246243
// the sample rate.
247244
uint32_t AdjustedSampleRate = UINT32_MAX;
248-
// Thread-local decrementing counter that indicates that a given allocation
249-
// should be sampled when it reaches zero.
250-
static TLS_INITIAL_EXEC uint64_t NextSampleCounter;
245+
246+
// Pack the thread local variables into a struct to ensure that they're in
247+
// the same cache line for performance reasons. These are the most touched
248+
// variables in GWP-ASan.
249+
struct alignas(8) ThreadLocalPackedVariables {
250+
constexpr ThreadLocalPackedVariables() {}
251+
// Thread-local decrementing counter that indicates that a given allocation
252+
// should be sampled when it reaches zero.
253+
uint32_t NextSampleCounter = 0;
254+
// Guard against recursivity. Unwinders often contain complex behaviour that
255+
// may not be safe for the allocator (i.e. the unwinder calls dlopen(),
256+
// which calls malloc()). When recursive behaviour is detected, we will
257+
// automatically fall back to the supporting allocator to supply the
258+
// allocation.
259+
bool RecursiveGuard = false;
260+
};
261+
static TLS_INITIAL_EXEC ThreadLocalPackedVariables ThreadLocals;
251262
};
252263
} // namespace gwp_asan
253264

0 commit comments

Comments
 (0)