Skip to content

Commit 8bbb229

Browse files
committed
[scudo] Add a config parameter to compile out quarantine code
In order to save code size, allow a config that does not suport quarantining to turn it off. Add tests to verify that disabling the quarantine code works properly.
1 parent c267928 commit 8bbb229

File tree

5 files changed

+178
-47
lines changed

5 files changed

+178
-47
lines changed

compiler-rt/lib/scudo/standalone/allocator_config.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ BASE_REQUIRED_TEMPLATE_TYPE(SecondaryT)
5454
// Indicates possible support for Memory Tagging.
5555
BASE_OPTIONAL(const bool, MaySupportMemoryTagging, false)
5656

57+
// Disable the quarantine code.
58+
BASE_OPTIONAL(const bool, QuarantineDisabled, false)
59+
5760
// PRIMARY_REQUIRED_TYPE(NAME)
5861
//
5962
// SizeClassMap to use with the Primary.

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ template <typename AllocatorConfig> struct PrimaryConfig {
6060
return BaseConfig<AllocatorConfig>::getMaySupportMemoryTagging();
6161
}
6262

63+
static constexpr bool getQuarantineDisabled() {
64+
return BaseConfig<AllocatorConfig>::getQuarantineDisabled();
65+
}
66+
6367
#define PRIMARY_REQUIRED_TYPE(NAME) \
6468
using NAME = typename AllocatorConfig::Primary::NAME;
6569

@@ -92,6 +96,10 @@ template <typename AllocatorConfig> struct SecondaryConfig {
9296
return BaseConfig<AllocatorConfig>::getMaySupportMemoryTagging();
9397
}
9498

99+
static constexpr bool getQuarantineDisabled() {
100+
return BaseConfig<AllocatorConfig>::getQuarantineDisabled();
101+
}
102+
95103
#define SECONDARY_REQUIRED_TEMPLATE_TYPE(NAME) \
96104
template <typename T> \
97105
using NAME = typename AllocatorConfig::Secondary::template NAME<T>;
@@ -111,6 +119,10 @@ template <typename AllocatorConfig> struct SecondaryConfig {
111119
return BaseConfig<AllocatorConfig>::getMaySupportMemoryTagging();
112120
}
113121

122+
static constexpr bool getQuarantineDisabled() {
123+
return BaseConfig<AllocatorConfig>::getQuarantineDisabled();
124+
}
125+
114126
#define SECONDARY_CACHE_OPTIONAL(TYPE, NAME, DEFAULT) \
115127
OPTIONAL_TEMPLATE(TYPE, NAME, DEFAULT, Cache::NAME) \
116128
static constexpr removeConst<TYPE>::type get##NAME() { \

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

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,10 @@ class Allocator {
184184
const s32 ReleaseToOsIntervalMs = getFlags()->release_to_os_interval_ms;
185185
Primary.init(ReleaseToOsIntervalMs);
186186
Secondary.init(&Stats, ReleaseToOsIntervalMs);
187-
Quarantine.init(
188-
static_cast<uptr>(getFlags()->quarantine_size_kb << 10),
189-
static_cast<uptr>(getFlags()->thread_local_quarantine_size_kb << 10));
187+
if (!AllocatorConfig::getQuarantineDisabled())
188+
Quarantine.init(
189+
static_cast<uptr>(getFlags()->quarantine_size_kb << 10),
190+
static_cast<uptr>(getFlags()->thread_local_quarantine_size_kb << 10));
190191
}
191192

192193
void enableRingBuffer() NO_THREAD_SAFETY_ANALYSIS {
@@ -276,16 +277,18 @@ class Allocator {
276277
// the last two items).
277278
void commitBack(TSD<ThisT> *TSD) {
278279
TSD->assertLocked(/*BypassCheck=*/true);
279-
Quarantine.drain(&TSD->getQuarantineCache(),
280-
QuarantineCallback(*this, TSD->getSizeClassAllocator()));
280+
if (!AllocatorConfig::getQuarantineDisabled())
281+
Quarantine.drain(&TSD->getQuarantineCache(),
282+
QuarantineCallback(*this, TSD->getSizeClassAllocator()));
281283
TSD->getSizeClassAllocator().destroy(&Stats);
282284
}
283285

284286
void drainCache(TSD<ThisT> *TSD) {
285287
TSD->assertLocked(/*BypassCheck=*/true);
286-
Quarantine.drainAndRecycle(
287-
&TSD->getQuarantineCache(),
288-
QuarantineCallback(*this, TSD->getSizeClassAllocator()));
288+
if (!AllocatorConfig::getQuarantineDisabled())
289+
Quarantine.drainAndRecycle(
290+
&TSD->getQuarantineCache(),
291+
QuarantineCallback(*this, TSD->getSizeClassAllocator()));
289292
TSD->getSizeClassAllocator().drain();
290293
}
291294
void drainCaches() { TSDRegistry.drainCaches(this); }
@@ -612,7 +615,8 @@ class Allocator {
612615
#endif
613616
TSDRegistry.disable();
614617
Stats.disable();
615-
Quarantine.disable();
618+
if (!AllocatorConfig::getQuarantineDisabled())
619+
Quarantine.disable();
616620
Primary.disable();
617621
Secondary.disable();
618622
disableRingBuffer();
@@ -623,7 +627,8 @@ class Allocator {
623627
enableRingBuffer();
624628
Secondary.enable();
625629
Primary.enable();
626-
Quarantine.enable();
630+
if (!AllocatorConfig::getQuarantineDisabled())
631+
Quarantine.enable();
627632
Stats.enable();
628633
TSDRegistry.enable();
629634
#ifdef GWP_ASAN_HOOKS
@@ -1252,7 +1257,8 @@ class Allocator {
12521257
// If the quarantine is disabled, the actual size of a chunk is 0 or larger
12531258
// than the maximum allowed, we return a chunk directly to the backend.
12541259
// This purposefully underflows for Size == 0.
1255-
const bool BypassQuarantine = !Quarantine.getCacheSize() ||
1260+
const bool BypassQuarantine = AllocatorConfig::getQuarantineDisabled() ||
1261+
!Quarantine.getCacheSize() ||
12561262
((Size - 1) >= QuarantineMaxChunkSize) ||
12571263
!Header->ClassId;
12581264
if (BypassQuarantine)
@@ -1642,7 +1648,8 @@ class Allocator {
16421648
uptr getStats(ScopedString *Str) {
16431649
Primary.getStats(Str);
16441650
Secondary.getStats(Str);
1645-
Quarantine.getStats(Str);
1651+
if (!AllocatorConfig::getQuarantineDisabled())
1652+
Quarantine.getStats(Str);
16461653
TSDRegistry.getStats(Str);
16471654
return Str->length();
16481655
}

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

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -302,26 +302,28 @@ class MapAllocatorCache {
302302
if (Entry.Time != 0)
303303
Entry.Time = Time;
304304

305-
if (useMemoryTagging<Config>(Options) && QuarantinePos == -1U) {
306-
// If we get here then memory tagging was disabled in between when we
307-
// read Options and when we locked Mutex. We can't insert our entry into
308-
// the quarantine or the cache because the permissions would be wrong so
309-
// just unmap it.
310-
unmapCallBack(Entry.MemMap);
311-
break;
312-
}
313-
if (Config::getQuarantineSize() && useMemoryTagging<Config>(Options)) {
314-
QuarantinePos =
315-
(QuarantinePos + 1) % Max(Config::getQuarantineSize(), 1u);
316-
if (!Quarantine[QuarantinePos].isValid()) {
305+
if (useMemoryTagging<Config>(Options)) {
306+
if (Config::getQuarantineDisabled() || QuarantinePos == -1U) {
307+
// If we get here then memory tagging was disabled in between when we
308+
// read Options and when we locked Mutex. We can't insert our entry
309+
// into the quarantine or the cache because the permissions would be
310+
// wrong so just unmap it.
311+
unmapCallBack(Entry.MemMap);
312+
break;
313+
}
314+
if (!Config::getQuarantineDisabled() && Config::getQuarantineSize()) {
315+
QuarantinePos =
316+
(QuarantinePos + 1) % Max(Config::getQuarantineSize(), 1u);
317+
if (!Quarantine[QuarantinePos].isValid()) {
318+
Quarantine[QuarantinePos] = Entry;
319+
return;
320+
}
321+
CachedBlock PrevEntry = Quarantine[QuarantinePos];
317322
Quarantine[QuarantinePos] = Entry;
318-
return;
323+
if (OldestTime == 0)
324+
OldestTime = Entry.Time;
325+
Entry = PrevEntry;
319326
}
320-
CachedBlock PrevEntry = Quarantine[QuarantinePos];
321-
Quarantine[QuarantinePos] = Entry;
322-
if (OldestTime == 0)
323-
OldestTime = Entry.Time;
324-
Entry = PrevEntry;
325327
}
326328

327329
// All excess entries are evicted from the cache. Note that when
@@ -506,16 +508,18 @@ class MapAllocatorCache {
506508

507509
void disableMemoryTagging() EXCLUDES(Mutex) {
508510
ScopedLock L(Mutex);
509-
for (u32 I = 0; I != Config::getQuarantineSize(); ++I) {
510-
if (Quarantine[I].isValid()) {
511-
MemMapT &MemMap = Quarantine[I].MemMap;
512-
unmapCallBack(MemMap);
513-
Quarantine[I].invalidate();
511+
if (!Config::getQuarantineDisabled()) {
512+
for (u32 I = 0; I != Config::getQuarantineSize(); ++I) {
513+
if (Quarantine[I].isValid()) {
514+
MemMapT &MemMap = Quarantine[I].MemMap;
515+
unmapCallBack(MemMap);
516+
Quarantine[I].invalidate();
517+
}
514518
}
519+
QuarantinePos = -1U;
515520
}
516521
for (CachedBlock &Entry : LRUEntries)
517522
Entry.MemMap.setMemoryPermission(Entry.CommitBase, Entry.CommitSize, 0);
518-
QuarantinePos = -1U;
519523
}
520524

521525
void disable() NO_THREAD_SAFETY_ANALYSIS { Mutex.lock(); }
@@ -572,8 +576,9 @@ class MapAllocatorCache {
572576
if (!LRUEntries.size() || OldestTime == 0 || OldestTime > Time)
573577
return;
574578
OldestTime = 0;
575-
for (uptr I = 0; I < Config::getQuarantineSize(); I++)
576-
releaseIfOlderThan(Quarantine[I], Time);
579+
if (!Config::getQuarantineDisabled())
580+
for (uptr I = 0; I < Config::getQuarantineSize(); I++)
581+
releaseIfOlderThan(Quarantine[I], Time);
577582
for (uptr I = 0; I < Config::getEntriesArraySize(); I++)
578583
releaseIfOlderThan(Entries[I], Time);
579584
}

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

Lines changed: 113 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -623,20 +623,20 @@ SCUDO_TYPED_TEST(ScudoCombinedDeathTest, DisableMemoryTagging) {
623623
SCUDO_TYPED_TEST(ScudoCombinedTest, Stats) {
624624
auto *Allocator = this->Allocator.get();
625625

626-
scudo::uptr BufferSize = 8192;
627-
std::vector<char> Buffer(BufferSize);
628-
scudo::uptr ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
629-
while (ActualSize > BufferSize) {
630-
BufferSize = ActualSize + 1024;
631-
Buffer.resize(BufferSize);
632-
ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
626+
std::string Stats(10000, '\0');
627+
scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());
628+
if (ActualSize > Stats.size()) {
629+
Stats.resize(ActualSize);
630+
ActualSize = Allocator->getStats(Stats.data(), Stats.size());
633631
}
634-
std::string Stats(Buffer.begin(), Buffer.end());
632+
EXPECT_GE(Stats.size(), ActualSize);
633+
635634
// Basic checks on the contents of the statistics output, which also allows us
636635
// to verify that we got it all.
637636
EXPECT_NE(Stats.find("Stats: SizeClassAllocator"), std::string::npos);
638637
EXPECT_NE(Stats.find("Stats: MapAllocator"), std::string::npos);
639-
EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos);
638+
// Do not explicitly check for quarantine stats since a config can disable
639+
// them. Other tests verify this (QuarantineEnabled/QuarantineDisabled).
640640
}
641641

642642
SCUDO_TYPED_TEST_SKIP_THREAD_SAFETY(ScudoCombinedTest, Drain) {
@@ -1076,3 +1076,107 @@ TEST(ScudoCombinedTest, BasicTrustyConfig) {
10761076

10771077
#endif
10781078
#endif
1079+
1080+
struct TestQuarantineSizeClassConfig {
1081+
static const scudo::uptr NumBits = 1;
1082+
static const scudo::uptr MinSizeLog = 10;
1083+
static const scudo::uptr MidSizeLog = 10;
1084+
static const scudo::uptr MaxSizeLog = 13;
1085+
static const scudo::u16 MaxNumCachedHint = 8;
1086+
static const scudo::uptr MaxBytesCachedLog = 12;
1087+
static const scudo::uptr SizeDelta = 0;
1088+
};
1089+
1090+
struct TestQuarantineConfig {
1091+
static const bool MaySupportMemoryTagging = false;
1092+
1093+
template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;
1094+
1095+
struct Primary {
1096+
// Tiny allocator, its Primary only serves chunks of four sizes.
1097+
using SizeClassMap = scudo::FixedSizeClassMap<DeathSizeClassConfig>;
1098+
static const scudo::uptr RegionSizeLog = DeathRegionSizeLog;
1099+
static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;
1100+
static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
1101+
typedef scudo::uptr CompactPtrT;
1102+
static const scudo::uptr CompactPtrScale = 0;
1103+
static const bool EnableRandomOffset = true;
1104+
static const scudo::uptr MapSizeIncrement = 1UL << 18;
1105+
static const scudo::uptr GroupSizeLog = 18;
1106+
};
1107+
template <typename Config>
1108+
using PrimaryT = scudo::SizeClassAllocator64<Config>;
1109+
1110+
struct Secondary {
1111+
template <typename Config>
1112+
using CacheT = scudo::MapAllocatorNoCache<Config>;
1113+
};
1114+
1115+
template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;
1116+
};
1117+
1118+
TEST(ScudoCombinedTest, QuarantineEnabled) {
1119+
// Enable quarantine options.
1120+
EXPECT_EQ(0, setenv("SCUDO_OPTIONS",
1121+
"quarantine_max_chunk_size=65535:quarantine_size_kb=500:"
1122+
"thread_local_quarantine_size_kb=600",
1123+
true));
1124+
1125+
using AllocatorT = scudo::Allocator<TestQuarantineConfig>;
1126+
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
1127+
1128+
void *P = Allocator->allocate(100U, Origin);
1129+
EXPECT_NE(P, nullptr);
1130+
Allocator->deallocate(P, Origin);
1131+
1132+
std::string Stats(10000, '\0');
1133+
scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());
1134+
if (ActualSize > Stats.size()) {
1135+
Stats.resize(ActualSize);
1136+
ActualSize = Allocator->getStats(Stats.data(), Stats.size());
1137+
}
1138+
EXPECT_GE(Stats.size(), ActualSize);
1139+
1140+
// Verify we get quarantine stats.
1141+
EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos);
1142+
// Verify that quarantine values get set properly.
1143+
EXPECT_NE(Stats.find("Quarantine limits: global: 500K; thread local: 600K"),
1144+
std::string::npos);
1145+
1146+
// Clear the options for all other tests.
1147+
EXPECT_EQ(0, setenv("SCUDO_OPTIONS", "", true));
1148+
}
1149+
1150+
struct TestQuarantineDisabledConfig : TestQuarantineConfig {
1151+
static const bool QuarantineDisabled = true;
1152+
};
1153+
1154+
TEST(ScudoCombinedTest, QuarantineDisabled) {
1155+
// Enable quarantine options.
1156+
EXPECT_EQ(0, setenv("SCUDO_OPTIONS",
1157+
"quarantine_max_chunk_size=65535:quarantine_size_kb=500:"
1158+
"thread_local_quarantine_size_kb=600",
1159+
true));
1160+
1161+
using AllocatorT = scudo::Allocator<TestQuarantineDisabledConfig>;
1162+
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
1163+
1164+
const scudo::uptr Size = 1000U;
1165+
void *P = Allocator->allocate(Size, Origin);
1166+
EXPECT_NE(P, nullptr);
1167+
Allocator->deallocate(P, Origin);
1168+
1169+
std::string Stats(10000, '\0');
1170+
scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());
1171+
if (ActualSize > Stats.size()) {
1172+
Stats.resize(ActualSize);
1173+
ActualSize = Allocator->getStats(Stats.data(), Stats.size());
1174+
}
1175+
EXPECT_GE(Stats.size(), ActualSize);
1176+
1177+
// No quarantine stats should exist because the code is not enabled.
1178+
EXPECT_EQ(Stats.find("Stats: Quarantine"), std::string::npos);
1179+
1180+
// Clear the options for all other tests.
1181+
EXPECT_EQ(0, setenv("SCUDO_OPTIONS", "", true));
1182+
}

0 commit comments

Comments
 (0)