1 //===-- combined_test.cpp ---------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "memtag.h" 10 #include "tests/scudo_unit_test.h" 11 12 #include "allocator_config.h" 13 #include "combined.h" 14 15 #include <condition_variable> 16 #include <memory> 17 #include <mutex> 18 #include <set> 19 #include <stdlib.h> 20 #include <thread> 21 #include <vector> 22 23 static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc; 24 static constexpr scudo::uptr MinAlignLog = FIRST_32_SECOND_64(3U, 4U); 25 26 // Fuchsia complains that the function is not used. 27 UNUSED static void disableDebuggerdMaybe() { 28 #if SCUDO_ANDROID 29 // Disable the debuggerd signal handler on Android, without this we can end 30 // up spending a significant amount of time creating tombstones. 31 signal(SIGSEGV, SIG_DFL); 32 #endif 33 } 34 35 template <class AllocatorT> 36 bool isPrimaryAllocation(scudo::uptr Size, scudo::uptr Alignment) { 37 const scudo::uptr MinAlignment = 1UL << SCUDO_MIN_ALIGNMENT_LOG; 38 if (Alignment < MinAlignment) 39 Alignment = MinAlignment; 40 const scudo::uptr NeededSize = 41 scudo::roundUpTo(Size, MinAlignment) + 42 ((Alignment > MinAlignment) ? Alignment : scudo::Chunk::getHeaderSize()); 43 return AllocatorT::PrimaryT::canAllocate(NeededSize); 44 } 45 46 template <class AllocatorT> 47 void checkMemoryTaggingMaybe(AllocatorT *Allocator, void *P, scudo::uptr Size, 48 scudo::uptr Alignment) { 49 const scudo::uptr MinAlignment = 1UL << SCUDO_MIN_ALIGNMENT_LOG; 50 Size = scudo::roundUpTo(Size, MinAlignment); 51 if (Allocator->useMemoryTaggingTestOnly()) 52 EXPECT_DEATH( 53 { 54 disableDebuggerdMaybe(); 55 reinterpret_cast<char *>(P)[-1] = 0xaa; 56 }, 57 ""); 58 if (isPrimaryAllocation<AllocatorT>(Size, Alignment) 59 ? Allocator->useMemoryTaggingTestOnly() 60 : Alignment == MinAlignment) { 61 EXPECT_DEATH( 62 { 63 disableDebuggerdMaybe(); 64 reinterpret_cast<char *>(P)[Size] = 0xaa; 65 }, 66 ""); 67 } 68 } 69 70 template <typename Config> struct TestAllocator : scudo::Allocator<Config> { 71 TestAllocator() { 72 this->initThreadMaybe(); 73 if (scudo::archSupportsMemoryTagging() && 74 !scudo::systemDetectsMemoryTagFaultsTestOnly()) 75 this->disableMemoryTagging(); 76 } 77 ~TestAllocator() { this->unmapTestOnly(); } 78 79 void *operator new(size_t size) { 80 void *p = nullptr; 81 EXPECT_EQ(0, posix_memalign(&p, alignof(TestAllocator), size)); 82 return p; 83 } 84 85 void operator delete(void *ptr) { free(ptr); } 86 }; 87 88 template <class TypeParam> struct ScudoCombinedTest : public Test { 89 ScudoCombinedTest() { 90 UseQuarantine = std::is_same<TypeParam, scudo::AndroidConfig>::value; 91 Allocator = std::make_unique<AllocatorT>(); 92 } 93 ~ScudoCombinedTest() { 94 Allocator->releaseToOS(); 95 UseQuarantine = true; 96 } 97 98 void RunTest(); 99 100 void BasicTest(scudo::uptr SizeLog); 101 102 using AllocatorT = TestAllocator<TypeParam>; 103 std::unique_ptr<AllocatorT> Allocator; 104 }; 105 106 template <typename T> using ScudoCombinedDeathTest = ScudoCombinedTest<T>; 107 108 #if SCUDO_FUCHSIA 109 #define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ 110 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \ 111 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, FuchsiaConfig) 112 #else 113 #define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ 114 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidSvelteConfig) \ 115 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, DefaultConfig) \ 116 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidConfig) 117 #endif 118 119 #define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \ 120 using FIXTURE##NAME##_##TYPE = FIXTURE##NAME<scudo::TYPE>; \ 121 TEST_F(FIXTURE##NAME##_##TYPE, NAME) { Run(); } 122 123 #define SCUDO_TYPED_TEST(FIXTURE, NAME) \ 124 template <class TypeParam> \ 125 struct FIXTURE##NAME : public FIXTURE<TypeParam> { \ 126 void Run(); \ 127 }; \ 128 SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \ 129 template <class TypeParam> void FIXTURE##NAME<TypeParam>::Run() 130 131 SCUDO_TYPED_TEST(ScudoCombinedTest, IsOwned) { 132 auto *Allocator = this->Allocator.get(); 133 static scudo::u8 StaticBuffer[scudo::Chunk::getHeaderSize() + 1]; 134 EXPECT_FALSE( 135 Allocator->isOwned(&StaticBuffer[scudo::Chunk::getHeaderSize()])); 136 137 scudo::u8 StackBuffer[scudo::Chunk::getHeaderSize() + 1]; 138 for (scudo::uptr I = 0; I < sizeof(StackBuffer); I++) 139 StackBuffer[I] = 0x42U; 140 EXPECT_FALSE(Allocator->isOwned(&StackBuffer[scudo::Chunk::getHeaderSize()])); 141 for (scudo::uptr I = 0; I < sizeof(StackBuffer); I++) 142 EXPECT_EQ(StackBuffer[I], 0x42U); 143 } 144 145 template <class Config> 146 void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) { 147 auto *Allocator = this->Allocator.get(); 148 149 // This allocates and deallocates a bunch of chunks, with a wide range of 150 // sizes and alignments, with a focus on sizes that could trigger weird 151 // behaviors (plus or minus a small delta of a power of two for example). 152 for (scudo::uptr AlignLog = MinAlignLog; AlignLog <= 16U; AlignLog++) { 153 const scudo::uptr Align = 1U << AlignLog; 154 for (scudo::sptr Delta = -32; Delta <= 32; Delta++) { 155 if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0) 156 continue; 157 const scudo::uptr Size = (1U << SizeLog) + Delta; 158 void *P = Allocator->allocate(Size, Origin, Align); 159 EXPECT_NE(P, nullptr); 160 EXPECT_TRUE(Allocator->isOwned(P)); 161 EXPECT_TRUE(scudo::isAligned(reinterpret_cast<scudo::uptr>(P), Align)); 162 EXPECT_LE(Size, Allocator->getUsableSize(P)); 163 memset(P, 0xaa, Size); 164 checkMemoryTaggingMaybe(Allocator, P, Size, Align); 165 Allocator->deallocate(P, Origin, Size); 166 } 167 } 168 } 169 170 #define SCUDO_MAKE_BASIC_TEST(SizeLog) \ 171 SCUDO_TYPED_TEST(ScudoCombinedDeathTest, BasicCombined##SizeLog) { \ 172 this->BasicTest(SizeLog); \ 173 } 174 175 SCUDO_MAKE_BASIC_TEST(0) 176 SCUDO_MAKE_BASIC_TEST(1) 177 SCUDO_MAKE_BASIC_TEST(2) 178 SCUDO_MAKE_BASIC_TEST(3) 179 SCUDO_MAKE_BASIC_TEST(4) 180 SCUDO_MAKE_BASIC_TEST(5) 181 SCUDO_MAKE_BASIC_TEST(6) 182 SCUDO_MAKE_BASIC_TEST(7) 183 SCUDO_MAKE_BASIC_TEST(8) 184 SCUDO_MAKE_BASIC_TEST(9) 185 SCUDO_MAKE_BASIC_TEST(10) 186 SCUDO_MAKE_BASIC_TEST(11) 187 SCUDO_MAKE_BASIC_TEST(12) 188 SCUDO_MAKE_BASIC_TEST(13) 189 SCUDO_MAKE_BASIC_TEST(14) 190 SCUDO_MAKE_BASIC_TEST(15) 191 SCUDO_MAKE_BASIC_TEST(16) 192 SCUDO_MAKE_BASIC_TEST(17) 193 SCUDO_MAKE_BASIC_TEST(18) 194 SCUDO_MAKE_BASIC_TEST(19) 195 SCUDO_MAKE_BASIC_TEST(20) 196 197 SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) { 198 auto *Allocator = this->Allocator.get(); 199 200 // Ensure that specifying ZeroContents returns a zero'd out block. 201 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) { 202 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) { 203 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U; 204 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true); 205 EXPECT_NE(P, nullptr); 206 for (scudo::uptr I = 0; I < Size; I++) 207 ASSERT_EQ((reinterpret_cast<char *>(P))[I], 0); 208 memset(P, 0xaa, Size); 209 Allocator->deallocate(P, Origin, Size); 210 } 211 } 212 } 213 214 SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) { 215 auto *Allocator = this->Allocator.get(); 216 217 // Ensure that specifying ZeroFill returns a zero'd out block. 218 Allocator->setFillContents(scudo::ZeroFill); 219 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) { 220 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) { 221 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U; 222 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false); 223 EXPECT_NE(P, nullptr); 224 for (scudo::uptr I = 0; I < Size; I++) 225 ASSERT_EQ((reinterpret_cast<char *>(P))[I], 0); 226 memset(P, 0xaa, Size); 227 Allocator->deallocate(P, Origin, Size); 228 } 229 } 230 } 231 232 SCUDO_TYPED_TEST(ScudoCombinedTest, PatternOrZeroFill) { 233 auto *Allocator = this->Allocator.get(); 234 235 // Ensure that specifying PatternOrZeroFill returns a pattern or zero filled 236 // block. The primary allocator only produces pattern filled blocks if MTE 237 // is disabled, so we only require pattern filled blocks in that case. 238 Allocator->setFillContents(scudo::PatternOrZeroFill); 239 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) { 240 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) { 241 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U; 242 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false); 243 EXPECT_NE(P, nullptr); 244 for (scudo::uptr I = 0; I < Size; I++) { 245 unsigned char V = (reinterpret_cast<unsigned char *>(P))[I]; 246 if (isPrimaryAllocation<TestAllocator<TypeParam>>(Size, 247 1U << MinAlignLog) && 248 !Allocator->useMemoryTaggingTestOnly()) 249 ASSERT_EQ(V, scudo::PatternFillByte); 250 else 251 ASSERT_TRUE(V == scudo::PatternFillByte || V == 0); 252 } 253 memset(P, 0xaa, Size); 254 Allocator->deallocate(P, Origin, Size); 255 } 256 } 257 } 258 259 SCUDO_TYPED_TEST(ScudoCombinedTest, BlockReuse) { 260 auto *Allocator = this->Allocator.get(); 261 262 // Verify that a chunk will end up being reused, at some point. 263 const scudo::uptr NeedleSize = 1024U; 264 void *NeedleP = Allocator->allocate(NeedleSize, Origin); 265 Allocator->deallocate(NeedleP, Origin); 266 bool Found = false; 267 for (scudo::uptr I = 0; I < 1024U && !Found; I++) { 268 void *P = Allocator->allocate(NeedleSize, Origin); 269 if (Allocator->getHeaderTaggedPointer(P) == 270 Allocator->getHeaderTaggedPointer(NeedleP)) 271 Found = true; 272 Allocator->deallocate(P, Origin); 273 } 274 EXPECT_TRUE(Found); 275 } 276 277 SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeIncreasing) { 278 auto *Allocator = this->Allocator.get(); 279 280 // Reallocate a chunk all the way up to a secondary allocation, verifying that 281 // we preserve the data in the process. 282 scudo::uptr Size = 16; 283 void *P = Allocator->allocate(Size, Origin); 284 const char Marker = 0xab; 285 memset(P, Marker, Size); 286 while (Size < TypeParam::Primary::SizeClassMap::MaxSize * 4) { 287 void *NewP = Allocator->reallocate(P, Size * 2); 288 EXPECT_NE(NewP, nullptr); 289 for (scudo::uptr J = 0; J < Size; J++) 290 EXPECT_EQ((reinterpret_cast<char *>(NewP))[J], Marker); 291 memset(reinterpret_cast<char *>(NewP) + Size, Marker, Size); 292 Size *= 2U; 293 P = NewP; 294 } 295 Allocator->deallocate(P, Origin); 296 } 297 298 SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeDecreasing) { 299 auto *Allocator = this->Allocator.get(); 300 301 // Reallocate a large chunk all the way down to a byte, verifying that we 302 // preserve the data in the process. 303 scudo::uptr Size = TypeParam::Primary::SizeClassMap::MaxSize * 2; 304 const scudo::uptr DataSize = 2048U; 305 void *P = Allocator->allocate(Size, Origin); 306 const char Marker = 0xab; 307 memset(P, Marker, scudo::Min(Size, DataSize)); 308 while (Size > 1U) { 309 Size /= 2U; 310 void *NewP = Allocator->reallocate(P, Size); 311 EXPECT_NE(NewP, nullptr); 312 for (scudo::uptr J = 0; J < scudo::Min(Size, DataSize); J++) 313 EXPECT_EQ((reinterpret_cast<char *>(NewP))[J], Marker); 314 P = NewP; 315 } 316 Allocator->deallocate(P, Origin); 317 } 318 319 SCUDO_TYPED_TEST(ScudoCombinedDeathTest, ReallocateSame) { 320 auto *Allocator = this->Allocator.get(); 321 322 // Check that reallocating a chunk to a slightly smaller or larger size 323 // returns the same chunk. This requires that all the sizes we iterate on use 324 // the same block size, but that should be the case for MaxSize - 64 with our 325 // default class size maps. 326 constexpr scudo::uptr ReallocSize = 327 TypeParam::Primary::SizeClassMap::MaxSize - 64; 328 void *P = Allocator->allocate(ReallocSize, Origin); 329 const char Marker = 0xab; 330 memset(P, Marker, ReallocSize); 331 for (scudo::sptr Delta = -32; Delta < 32; Delta += 8) { 332 const scudo::uptr NewSize = ReallocSize + Delta; 333 void *NewP = Allocator->reallocate(P, NewSize); 334 EXPECT_EQ(NewP, P); 335 for (scudo::uptr I = 0; I < ReallocSize - 32; I++) 336 EXPECT_EQ((reinterpret_cast<char *>(NewP))[I], Marker); 337 checkMemoryTaggingMaybe(Allocator, NewP, NewSize, 0); 338 } 339 Allocator->deallocate(P, Origin); 340 } 341 342 SCUDO_TYPED_TEST(ScudoCombinedTest, IterateOverChunks) { 343 auto *Allocator = this->Allocator.get(); 344 // Allocates a bunch of chunks, then iterate over all the chunks, ensuring 345 // they are the ones we allocated. This requires the allocator to not have any 346 // other allocated chunk at this point (eg: won't work with the Quarantine). 347 // FIXME: Make it work with UseQuarantine and tagging enabled. Internals of 348 // iterateOverChunks reads header by tagged and non-tagger pointers so one of 349 // them will fail. 350 if (!UseQuarantine) { 351 std::vector<void *> V; 352 for (scudo::uptr I = 0; I < 64U; I++) 353 V.push_back(Allocator->allocate( 354 rand() % (TypeParam::Primary::SizeClassMap::MaxSize / 2U), Origin)); 355 Allocator->disable(); 356 Allocator->iterateOverChunks( 357 0U, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1), 358 [](uintptr_t Base, size_t Size, void *Arg) { 359 std::vector<void *> *V = reinterpret_cast<std::vector<void *> *>(Arg); 360 void *P = reinterpret_cast<void *>(Base); 361 EXPECT_NE(std::find(V->begin(), V->end(), P), V->end()); 362 }, 363 reinterpret_cast<void *>(&V)); 364 Allocator->enable(); 365 for (auto P : V) 366 Allocator->deallocate(P, Origin); 367 } 368 } 369 370 SCUDO_TYPED_TEST(ScudoCombinedDeathTest, UseAfterFree) { 371 auto *Allocator = this->Allocator.get(); 372 373 // Check that use-after-free is detected. 374 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) { 375 const scudo::uptr Size = 1U << SizeLog; 376 if (!Allocator->useMemoryTaggingTestOnly()) 377 continue; 378 EXPECT_DEATH( 379 { 380 disableDebuggerdMaybe(); 381 void *P = Allocator->allocate(Size, Origin); 382 Allocator->deallocate(P, Origin); 383 reinterpret_cast<char *>(P)[0] = 0xaa; 384 }, 385 ""); 386 EXPECT_DEATH( 387 { 388 disableDebuggerdMaybe(); 389 void *P = Allocator->allocate(Size, Origin); 390 Allocator->deallocate(P, Origin); 391 reinterpret_cast<char *>(P)[Size - 1] = 0xaa; 392 }, 393 ""); 394 } 395 } 396 397 SCUDO_TYPED_TEST(ScudoCombinedDeathTest, DisableMemoryTagging) { 398 auto *Allocator = this->Allocator.get(); 399 400 if (Allocator->useMemoryTaggingTestOnly()) { 401 // Check that disabling memory tagging works correctly. 402 void *P = Allocator->allocate(2048, Origin); 403 EXPECT_DEATH(reinterpret_cast<char *>(P)[2048] = 0xaa, ""); 404 scudo::ScopedDisableMemoryTagChecks NoTagChecks; 405 Allocator->disableMemoryTagging(); 406 reinterpret_cast<char *>(P)[2048] = 0xaa; 407 Allocator->deallocate(P, Origin); 408 409 P = Allocator->allocate(2048, Origin); 410 EXPECT_EQ(scudo::untagPointer(P), P); 411 reinterpret_cast<char *>(P)[2048] = 0xaa; 412 Allocator->deallocate(P, Origin); 413 414 Allocator->releaseToOS(); 415 } 416 } 417 418 SCUDO_TYPED_TEST(ScudoCombinedTest, Stats) { 419 auto *Allocator = this->Allocator.get(); 420 421 scudo::uptr BufferSize = 8192; 422 std::vector<char> Buffer(BufferSize); 423 scudo::uptr ActualSize = Allocator->getStats(Buffer.data(), BufferSize); 424 while (ActualSize > BufferSize) { 425 BufferSize = ActualSize + 1024; 426 Buffer.resize(BufferSize); 427 ActualSize = Allocator->getStats(Buffer.data(), BufferSize); 428 } 429 std::string Stats(Buffer.begin(), Buffer.end()); 430 // Basic checks on the contents of the statistics output, which also allows us 431 // to verify that we got it all. 432 EXPECT_NE(Stats.find("Stats: SizeClassAllocator"), std::string::npos); 433 EXPECT_NE(Stats.find("Stats: MapAllocator"), std::string::npos); 434 EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos); 435 } 436 437 SCUDO_TYPED_TEST(ScudoCombinedTest, CacheDrain) { 438 auto *Allocator = this->Allocator.get(); 439 440 std::vector<void *> V; 441 for (scudo::uptr I = 0; I < 64U; I++) 442 V.push_back(Allocator->allocate( 443 rand() % (TypeParam::Primary::SizeClassMap::MaxSize / 2U), Origin)); 444 for (auto P : V) 445 Allocator->deallocate(P, Origin); 446 447 bool UnlockRequired; 448 auto *TSD = Allocator->getTSDRegistry()->getTSDAndLock(&UnlockRequired); 449 EXPECT_TRUE(!TSD->Cache.isEmpty()); 450 TSD->Cache.drain(); 451 EXPECT_TRUE(TSD->Cache.isEmpty()); 452 if (UnlockRequired) 453 TSD->unlock(); 454 } 455 456 SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) { 457 std::mutex Mutex; 458 std::condition_variable Cv; 459 bool Ready = false; 460 auto *Allocator = this->Allocator.get(); 461 std::thread Threads[32]; 462 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++) 463 Threads[I] = std::thread([&]() { 464 { 465 std::unique_lock<std::mutex> Lock(Mutex); 466 while (!Ready) 467 Cv.wait(Lock); 468 } 469 std::vector<std::pair<void *, scudo::uptr>> V; 470 for (scudo::uptr I = 0; I < 256U; I++) { 471 const scudo::uptr Size = std::rand() % 4096U; 472 void *P = Allocator->allocate(Size, Origin); 473 // A region could have ran out of memory, resulting in a null P. 474 if (P) 475 V.push_back(std::make_pair(P, Size)); 476 } 477 while (!V.empty()) { 478 auto Pair = V.back(); 479 Allocator->deallocate(Pair.first, Origin, Pair.second); 480 V.pop_back(); 481 } 482 }); 483 { 484 std::unique_lock<std::mutex> Lock(Mutex); 485 Ready = true; 486 Cv.notify_all(); 487 } 488 for (auto &T : Threads) 489 T.join(); 490 Allocator->releaseToOS(); 491 } 492 493 // Test that multiple instantiations of the allocator have not messed up the 494 // process's signal handlers (GWP-ASan used to do this). 495 TEST(ScudoCombinedDeathTest, SKIP_ON_FUCHSIA(testSEGV)) { 496 const scudo::uptr Size = 4 * scudo::getPageSizeCached(); 497 scudo::MapPlatformData Data = {}; 498 void *P = scudo::map(nullptr, Size, "testSEGV", MAP_NOACCESS, &Data); 499 EXPECT_NE(P, nullptr); 500 EXPECT_DEATH(memset(P, 0xaa, Size), ""); 501 scudo::unmap(P, Size, UNMAP_ALL, &Data); 502 } 503 504 struct DeathSizeClassConfig { 505 static const scudo::uptr NumBits = 1; 506 static const scudo::uptr MinSizeLog = 10; 507 static const scudo::uptr MidSizeLog = 10; 508 static const scudo::uptr MaxSizeLog = 13; 509 static const scudo::u32 MaxNumCachedHint = 4; 510 static const scudo::uptr MaxBytesCachedLog = 12; 511 static const scudo::uptr SizeDelta = 0; 512 }; 513 514 static const scudo::uptr DeathRegionSizeLog = 20U; 515 struct DeathConfig { 516 static const bool MaySupportMemoryTagging = false; 517 518 // Tiny allocator, its Primary only serves chunks of four sizes. 519 using SizeClassMap = scudo::FixedSizeClassMap<DeathSizeClassConfig>; 520 typedef scudo::SizeClassAllocator64<DeathConfig> Primary; 521 static const scudo::uptr PrimaryRegionSizeLog = DeathRegionSizeLog; 522 static const scudo::s32 PrimaryMinReleaseToOsIntervalMs = INT32_MIN; 523 static const scudo::s32 PrimaryMaxReleaseToOsIntervalMs = INT32_MAX; 524 typedef scudo::uptr PrimaryCompactPtrT; 525 static const scudo::uptr PrimaryCompactPtrScale = 0; 526 static const bool PrimaryEnableRandomOffset = true; 527 static const scudo::uptr PrimaryMapSizeIncrement = 1UL << 18; 528 529 typedef scudo::MapAllocatorNoCache SecondaryCache; 530 template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>; 531 }; 532 533 TEST(ScudoCombinedDeathTest, DeathCombined) { 534 using AllocatorT = TestAllocator<DeathConfig>; 535 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT()); 536 537 const scudo::uptr Size = 1000U; 538 void *P = Allocator->allocate(Size, Origin); 539 EXPECT_NE(P, nullptr); 540 541 // Invalid sized deallocation. 542 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), ""); 543 544 // Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available. 545 UNUSED void *MisalignedP = 546 reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U); 547 EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), ""); 548 EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), ""); 549 550 // Header corruption. 551 scudo::u64 *H = 552 reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P)); 553 *H ^= 0x42U; 554 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); 555 *H ^= 0x420042U; 556 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); 557 *H ^= 0x420000U; 558 559 // Invalid chunk state. 560 Allocator->deallocate(P, Origin, Size); 561 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); 562 EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), ""); 563 EXPECT_DEATH(Allocator->getUsableSize(P), ""); 564 } 565 566 // Verify that when a region gets full, the allocator will still manage to 567 // fulfill the allocation through a larger size class. 568 TEST(ScudoCombinedTest, FullRegion) { 569 using AllocatorT = TestAllocator<DeathConfig>; 570 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT()); 571 572 std::vector<void *> V; 573 scudo::uptr FailedAllocationsCount = 0; 574 for (scudo::uptr ClassId = 1U; 575 ClassId <= DeathConfig::SizeClassMap::LargestClassId; ClassId++) { 576 const scudo::uptr Size = 577 DeathConfig::SizeClassMap::getSizeByClassId(ClassId); 578 // Allocate enough to fill all of the regions above this one. 579 const scudo::uptr MaxNumberOfChunks = 580 ((1U << DeathRegionSizeLog) / Size) * 581 (DeathConfig::SizeClassMap::LargestClassId - ClassId + 1); 582 void *P; 583 for (scudo::uptr I = 0; I <= MaxNumberOfChunks; I++) { 584 P = Allocator->allocate(Size - 64U, Origin); 585 if (!P) 586 FailedAllocationsCount++; 587 else 588 V.push_back(P); 589 } 590 while (!V.empty()) { 591 Allocator->deallocate(V.back(), Origin); 592 V.pop_back(); 593 } 594 } 595 EXPECT_EQ(FailedAllocationsCount, 0U); 596 } 597 598 // Ensure that releaseToOS can be called prior to any other allocator 599 // operation without issue. 600 SCUDO_TYPED_TEST(ScudoCombinedTest, ReleaseToOS) { 601 auto *Allocator = this->Allocator.get(); 602 Allocator->releaseToOS(); 603 } 604 605 SCUDO_TYPED_TEST(ScudoCombinedTest, OddEven) { 606 auto *Allocator = this->Allocator.get(); 607 608 if (!Allocator->useMemoryTaggingTestOnly()) 609 return; 610 611 auto CheckOddEven = [](scudo::uptr P1, scudo::uptr P2) { 612 scudo::uptr Tag1 = scudo::extractTag(scudo::loadTag(P1)); 613 scudo::uptr Tag2 = scudo::extractTag(scudo::loadTag(P2)); 614 EXPECT_NE(Tag1 % 2, Tag2 % 2); 615 }; 616 617 using SizeClassMap = typename TypeParam::Primary::SizeClassMap; 618 for (scudo::uptr ClassId = 1U; ClassId <= SizeClassMap::LargestClassId; 619 ClassId++) { 620 const scudo::uptr Size = SizeClassMap::getSizeByClassId(ClassId); 621 622 std::set<scudo::uptr> Ptrs; 623 bool Found = false; 624 for (unsigned I = 0; I != 65536; ++I) { 625 scudo::uptr P = scudo::untagPointer(reinterpret_cast<scudo::uptr>( 626 Allocator->allocate(Size - scudo::Chunk::getHeaderSize(), Origin))); 627 if (Ptrs.count(P - Size)) { 628 Found = true; 629 CheckOddEven(P, P - Size); 630 break; 631 } 632 if (Ptrs.count(P + Size)) { 633 Found = true; 634 CheckOddEven(P, P + Size); 635 break; 636 } 637 Ptrs.insert(P); 638 } 639 EXPECT_TRUE(Found); 640 } 641 } 642 643 SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) { 644 auto *Allocator = this->Allocator.get(); 645 646 std::vector<void *> Ptrs(65536, nullptr); 647 648 Allocator->setOption(scudo::Option::ThreadDisableMemInit, 1); 649 650 constexpr scudo::uptr MinAlignLog = FIRST_32_SECOND_64(3U, 4U); 651 652 // Test that if mem-init is disabled on a thread, calloc should still work as 653 // expected. This is tricky to ensure when MTE is enabled, so this test tries 654 // to exercise the relevant code on our MTE path. 655 for (scudo::uptr ClassId = 1U; ClassId <= 8; ClassId++) { 656 using SizeClassMap = typename TypeParam::Primary::SizeClassMap; 657 const scudo::uptr Size = 658 SizeClassMap::getSizeByClassId(ClassId) - scudo::Chunk::getHeaderSize(); 659 if (Size < 8) 660 continue; 661 for (unsigned I = 0; I != Ptrs.size(); ++I) { 662 Ptrs[I] = Allocator->allocate(Size, Origin); 663 memset(Ptrs[I], 0xaa, Size); 664 } 665 for (unsigned I = 0; I != Ptrs.size(); ++I) 666 Allocator->deallocate(Ptrs[I], Origin, Size); 667 for (unsigned I = 0; I != Ptrs.size(); ++I) { 668 Ptrs[I] = Allocator->allocate(Size - 8, Origin); 669 memset(Ptrs[I], 0xbb, Size - 8); 670 } 671 for (unsigned I = 0; I != Ptrs.size(); ++I) 672 Allocator->deallocate(Ptrs[I], Origin, Size - 8); 673 for (unsigned I = 0; I != Ptrs.size(); ++I) { 674 Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true); 675 for (scudo::uptr J = 0; J < Size; ++J) 676 ASSERT_EQ((reinterpret_cast<char *>(Ptrs[I]))[J], 0); 677 } 678 } 679 680 Allocator->setOption(scudo::Option::ThreadDisableMemInit, 0); 681 } 682