Skip to content

Commit ea2036e

Browse files
authored
[scudo] Fix the use of ASSERT_CAPABILITY in TSD (#68273)
In getCache()/getQuarantineCache(), they return a reference to variable guarded by a mutex. After #67776, thread-safey analysis checks if a variable return by reference has the lock held. The ASSERT_CAPABILITY only claims after calling that function, the lock will be held. But not asserting that the lock is held *before* calling that function. In the patch, we switch to use REQUIRES() and assertLocked() to mark the code paths. Also remove the misused ASSERT_CAPABILITY. Fixes #67795, #67796
1 parent 9bbd2bf commit ea2036e

File tree

5 files changed

+27
-7
lines changed

5 files changed

+27
-7
lines changed

compiler-rt/lib/scudo/standalone/combined.h

+5
Original file line numberDiff line numberDiff line change
@@ -245,12 +245,14 @@ class Allocator {
245245
// - unlinking the local stats from the global ones (destroying the cache does
246246
// the last two items).
247247
void commitBack(TSD<ThisT> *TSD) {
248+
TSD->assertLocked(/*BypassCheck=*/true);
248249
Quarantine.drain(&TSD->getQuarantineCache(),
249250
QuarantineCallback(*this, TSD->getCache()));
250251
TSD->getCache().destroy(&Stats);
251252
}
252253

253254
void drainCache(TSD<ThisT> *TSD) {
255+
TSD->assertLocked(/*BypassCheck=*/true);
254256
Quarantine.drainAndRecycle(&TSD->getQuarantineCache(),
255257
QuarantineCallback(*this, TSD->getCache()));
256258
TSD->getCache().drain();
@@ -363,6 +365,7 @@ class Allocator {
363365
DCHECK_NE(ClassId, 0U);
364366
bool UnlockRequired;
365367
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
368+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
366369
Block = TSD->getCache().allocate(ClassId);
367370
// If the allocation failed, the most likely reason with a 32-bit primary
368371
// is the region being full. In that event, retry in each successively
@@ -1147,6 +1150,7 @@ class Allocator {
11471150
if (LIKELY(ClassId)) {
11481151
bool UnlockRequired;
11491152
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
1153+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
11501154
const bool CacheDrained =
11511155
TSD->getCache().deallocate(ClassId, BlockBegin);
11521156
if (UnlockRequired)
@@ -1166,6 +1170,7 @@ class Allocator {
11661170
} else {
11671171
bool UnlockRequired;
11681172
auto *TSD = TSDRegistry.getTSDAndLock(&UnlockRequired);
1173+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
11691174
Quarantine.put(&TSD->getQuarantineCache(),
11701175
QuarantineCallback(*this, TSD->getCache()), Ptr, Size);
11711176
if (UnlockRequired)

compiler-rt/lib/scudo/standalone/tests/combined_test.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, CacheDrain) NO_THREAD_SAFETY_ANALYSIS {
512512

513513
bool UnlockRequired;
514514
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
515+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
515516
EXPECT_TRUE(!TSD->getCache().isEmpty());
516517
TSD->getCache().drain();
517518
EXPECT_TRUE(TSD->getCache().isEmpty());
@@ -536,6 +537,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ForceCacheDrain) NO_THREAD_SAFETY_ANALYSIS {
536537

537538
bool UnlockRequired;
538539
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
540+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
539541
EXPECT_TRUE(TSD->getCache().isEmpty());
540542
EXPECT_EQ(TSD->getQuarantineCache().getSize(), 0U);
541543
EXPECT_TRUE(Allocator->getQuarantine()->isEmpty());
@@ -842,6 +844,7 @@ TEST(ScudoCombinedTest, BasicTrustyConfig) {
842844

843845
bool UnlockRequired;
844846
auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired);
847+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
845848
TSD->getCache().drain();
846849

847850
Allocator->releaseToOS(scudo::ReleaseToOS::Force);

compiler-rt/lib/scudo/standalone/tests/tsd_test.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,15 @@ template <class AllocatorT> static void testRegistry() {
101101

102102
bool UnlockRequired;
103103
auto TSD = Registry->getTSDAndLock(&UnlockRequired);
104+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
104105
EXPECT_NE(TSD, nullptr);
105106
EXPECT_EQ(TSD->getCache().Canary, 0U);
106107
if (UnlockRequired)
107108
TSD->unlock();
108109

109110
Registry->initThreadMaybe(Allocator.get(), /*MinimalInit=*/false);
110111
TSD = Registry->getTSDAndLock(&UnlockRequired);
112+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
111113
EXPECT_NE(TSD, nullptr);
112114
EXPECT_EQ(TSD->getCache().Canary, 0U);
113115
memset(&TSD->getCache(), 0x42, sizeof(TSD->getCache()));
@@ -137,6 +139,7 @@ template <typename AllocatorT> static void stressCache(AllocatorT *Allocator) {
137139
Registry->initThreadMaybe(Allocator, /*MinimalInit=*/false);
138140
bool UnlockRequired;
139141
auto TSD = Registry->getTSDAndLock(&UnlockRequired);
142+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
140143
EXPECT_NE(TSD, nullptr);
141144
// For an exclusive TSD, the cache should be empty. We cannot guarantee the
142145
// same for a shared TSD.
@@ -195,6 +198,7 @@ static void stressSharedRegistry(MockAllocator<SharedCaches> *Allocator) {
195198
bool UnlockRequired;
196199
for (scudo::uptr I = 0; I < 4096U; I++) {
197200
auto TSD = Registry->getTSDAndLock(&UnlockRequired);
201+
TSD->assertLocked(/*BypassCheck=*/!UnlockRequired);
198202
EXPECT_NE(TSD, nullptr);
199203
Set.insert(reinterpret_cast<void *>(TSD));
200204
if (UnlockRequired)

compiler-rt/lib/scudo/standalone/tsd.h

+10-7
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,14 @@ template <class Allocator> struct alignas(SCUDO_CACHE_LINE_SIZE) TSD {
5353
inline void unlock() NO_THREAD_SAFETY_ANALYSIS { Mutex.unlock(); }
5454
inline uptr getPrecedence() { return atomic_load_relaxed(&Precedence); }
5555

56-
void commitBack(Allocator *Instance) ASSERT_CAPABILITY(Mutex) {
57-
Instance->commitBack(this);
56+
void commitBack(Allocator *Instance) { Instance->commitBack(this); }
57+
58+
// As the comments attached to `getCache()`, the TSD doesn't always need to be
59+
// locked. In that case, we would only skip the check before we have all TSDs
60+
// locked in all paths.
61+
void assertLocked(bool BypassCheck) ASSERT_CAPABILITY(Mutex) {
62+
if (SCUDO_DEBUG && !BypassCheck)
63+
Mutex.assertHeld();
5864
}
5965

6066
// Ideally, we may want to assert that all the operations on
@@ -66,11 +72,8 @@ template <class Allocator> struct alignas(SCUDO_CACHE_LINE_SIZE) TSD {
6672
// TODO(chiahungduan): Ideally, we want to do `Mutex.assertHeld` but acquiring
6773
// TSD doesn't always require holding the lock. Add this assertion while the
6874
// lock is always acquired.
69-
typename Allocator::CacheT &getCache() ASSERT_CAPABILITY(Mutex) {
70-
return Cache;
71-
}
72-
typename Allocator::QuarantineCacheT &getQuarantineCache()
73-
ASSERT_CAPABILITY(Mutex) {
75+
typename Allocator::CacheT &getCache() REQUIRES(Mutex) { return Cache; }
76+
typename Allocator::QuarantineCacheT &getQuarantineCache() REQUIRES(Mutex) {
7477
return QuarantineCache;
7578
}
7679

compiler-rt/lib/scudo/standalone/tsd_shared.h

+5
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ struct TSDRegistrySharedT {
120120
TSDsArraySize);
121121
for (uptr I = 0; I < NumberOfTSDs; ++I) {
122122
TSDs[I].lock();
123+
// Theoretically, we want to mark TSD::lock()/TSD::unlock() with proper
124+
// thread annotations. However, given the TSD is only locked on shared
125+
// path, do the assertion in a separate path to avoid confusing the
126+
// analyzer.
127+
TSDs[I].assertLocked(/*BypassCheck=*/true);
123128
Str->append(" Shared TSD[%zu]:\n", I);
124129
TSDs[I].getCache().getStats(Str);
125130
TSDs[I].unlock();

0 commit comments

Comments
 (0)