1429 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "memtag.h"10#include "stack_depot.h"11#include "tests/scudo_unit_test.h"12 13#include "allocator_config.h"14#include "chunk.h"15#include "combined.h"16#include "condition_variable.h"17#include "mem_map.h"18#include "size_class_map.h"19 20#include <algorithm>21#include <condition_variable>22#include <memory>23#include <mutex>24#include <set>25#include <stdlib.h>26#include <thread>27#include <unordered_map>28#include <vector>29 30static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc;31static constexpr scudo::uptr MinAlignLog = FIRST_32_SECOND_64(3U, 4U);32 33// Fuchsia complains that the function is not used.34UNUSED static void disableDebuggerdMaybe() {35#if SCUDO_ANDROID36 // Disable the debuggerd signal handler on Android, without this we can end37 // up spending a significant amount of time creating tombstones.38 signal(SIGSEGV, SIG_DFL);39#endif40}41 42template <class AllocatorT>43bool isPrimaryAllocation(scudo::uptr Size, scudo::uptr Alignment) {44 const scudo::uptr MinAlignment = 1UL << SCUDO_MIN_ALIGNMENT_LOG;45 if (Alignment < MinAlignment)46 Alignment = MinAlignment;47 const scudo::uptr NeededSize =48 scudo::roundUp(Size, MinAlignment) +49 ((Alignment > MinAlignment) ? Alignment : scudo::Chunk::getHeaderSize());50 return AllocatorT::PrimaryT::canAllocate(NeededSize);51}52 53template <class AllocatorT>54void checkMemoryTaggingMaybe(AllocatorT *Allocator, void *P, scudo::uptr Size,55 scudo::uptr Alignment) {56 const scudo::uptr MinAlignment = 1UL << SCUDO_MIN_ALIGNMENT_LOG;57 Size = scudo::roundUp(Size, MinAlignment);58 if (Allocator->useMemoryTaggingTestOnly()) {59 EXPECT_DEATH(60 {61 disableDebuggerdMaybe();62 reinterpret_cast<char *>(P)[-1] = 'A';63 },64 "");65 }66 if (isPrimaryAllocation<AllocatorT>(Size, Alignment)67 ? Allocator->useMemoryTaggingTestOnly()68 : Alignment == MinAlignment &&69 AllocatorT::SecondaryT::getGuardPageSize() > 0) {70 EXPECT_DEATH(71 {72 disableDebuggerdMaybe();73 reinterpret_cast<char *>(P)[Size] = 'A';74 },75 "");76 }77}78 79template <typename Config> struct TestAllocator : scudo::Allocator<Config> {80 TestAllocator() {81 this->initThreadMaybe();82 if (scudo::archSupportsMemoryTagging() &&83 !scudo::systemDetectsMemoryTagFaultsTestOnly())84 this->disableMemoryTagging();85 }86 ~TestAllocator() { this->unmapTestOnly(); }87 88 void *operator new(size_t size);89 void operator delete(void *ptr);90};91 92constexpr size_t kMaxAlign = std::max({93 alignof(scudo::Allocator<scudo::DefaultConfig>),94#if SCUDO_CAN_USE_PRIMARY6495 alignof(scudo::Allocator<scudo::FuchsiaConfig>),96#endif97 alignof(scudo::Allocator<scudo::AndroidConfig>)98});99 100#if SCUDO_RISCV64101// The allocator is over 4MB large. Rather than creating an instance of this on102// the heap, keep it in a global storage to reduce fragmentation from having to103// mmap this at the start of every test.104struct TestAllocatorStorage {105 static constexpr size_t kMaxSize = std::max({106 sizeof(scudo::Allocator<scudo::DefaultConfig>),107#if SCUDO_CAN_USE_PRIMARY64108 sizeof(scudo::Allocator<scudo::FuchsiaConfig>),109#endif110 sizeof(scudo::Allocator<scudo::AndroidConfig>)111 });112 113 // To alleviate some problem, let's skip the thread safety analysis here.114 static void *get(size_t size) NO_THREAD_SAFETY_ANALYSIS {115 CHECK(size <= kMaxSize &&116 "Allocation size doesn't fit in the allocator storage");117 M.lock();118 return AllocatorStorage;119 }120 121 static void release(void *ptr) NO_THREAD_SAFETY_ANALYSIS {122 M.assertHeld();123 M.unlock();124 ASSERT_EQ(ptr, AllocatorStorage);125 }126 127 static scudo::HybridMutex M;128 static uint8_t AllocatorStorage[kMaxSize];129};130scudo::HybridMutex TestAllocatorStorage::M;131alignas(kMaxAlign) uint8_t TestAllocatorStorage::AllocatorStorage[kMaxSize];132#else133struct TestAllocatorStorage {134 static void *get(size_t size) NO_THREAD_SAFETY_ANALYSIS {135 void *p = nullptr;136 EXPECT_EQ(0, posix_memalign(&p, kMaxAlign, size));137 return p;138 }139 static void release(void *ptr) NO_THREAD_SAFETY_ANALYSIS { free(ptr); }140};141#endif142 143template <typename Config>144void *TestAllocator<Config>::operator new(size_t size) {145 return TestAllocatorStorage::get(size);146}147 148template <typename Config>149void TestAllocator<Config>::operator delete(void *ptr) {150 TestAllocatorStorage::release(ptr);151}152 153template <class TypeParam> struct ScudoCombinedTest : public Test {154 ScudoCombinedTest() { Allocator = std::make_unique<AllocatorT>(); }155 ~ScudoCombinedTest() { Allocator->releaseToOS(scudo::ReleaseToOS::Force); }156 157 void RunTest();158 159 void BasicTest(scudo::uptr SizeLog);160 161 using AllocatorT = TestAllocator<TypeParam>;162 std::unique_ptr<AllocatorT> Allocator;163};164 165template <typename T> using ScudoCombinedDeathTest = ScudoCombinedTest<T>;166 167namespace scudo {168struct TestConditionVariableConfig {169 static const bool MaySupportMemoryTagging = true;170 template <class A>171 using TSDRegistryT =172 scudo::TSDRegistrySharedT<A, 8U, 4U>; // Shared, max 8 TSDs.173 174 struct Primary {175 using SizeClassMap = scudo::AndroidSizeClassMap;176#if SCUDO_CAN_USE_PRIMARY64177 static const scudo::uptr RegionSizeLog = 28U;178 typedef scudo::u32 CompactPtrT;179 static const scudo::uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;180 static const scudo::uptr GroupSizeLog = 20U;181 static const bool EnableRandomOffset = true;182 static const scudo::uptr MapSizeIncrement = 1UL << 18;183#else184 static const scudo::uptr RegionSizeLog = 18U;185 static const scudo::uptr GroupSizeLog = 18U;186 typedef scudo::uptr CompactPtrT;187#endif188 static const scudo::s32 MinReleaseToOsIntervalMs = 1000;189 static const scudo::s32 MaxReleaseToOsIntervalMs = 1000;190#if SCUDO_LINUX191 using ConditionVariableT = scudo::ConditionVariableLinux;192#else193 using ConditionVariableT = scudo::ConditionVariableDummy;194#endif195 };196#if SCUDO_CAN_USE_PRIMARY64197 template <typename Config>198 using PrimaryT = scudo::SizeClassAllocator64<Config>;199#else200 template <typename Config>201 using PrimaryT = scudo::SizeClassAllocator32<Config>;202#endif203 204 struct Secondary {205 template <typename Config>206 using CacheT = scudo::MapAllocatorNoCache<Config>;207 };208 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;209};210 211struct TestNoCacheConfig {212 static const bool MaySupportMemoryTagging = true;213 template <class A>214 using TSDRegistryT =215 scudo::TSDRegistrySharedT<A, 8U, 4U>; // Shared, max 8 TSDs.216 217 struct Primary {218 using SizeClassMap = scudo::AndroidSizeClassMap;219#if SCUDO_CAN_USE_PRIMARY64220 static const scudo::uptr RegionSizeLog = 28U;221 typedef scudo::u32 CompactPtrT;222 static const scudo::uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;223 static const scudo::uptr GroupSizeLog = 20U;224 static const bool EnableRandomOffset = true;225 static const scudo::uptr MapSizeIncrement = 1UL << 18;226#else227 static const scudo::uptr RegionSizeLog = 18U;228 static const scudo::uptr GroupSizeLog = 18U;229 typedef scudo::uptr CompactPtrT;230#endif231 static const bool EnableBlockCache = false;232 static const scudo::s32 MinReleaseToOsIntervalMs = 1000;233 static const scudo::s32 MaxReleaseToOsIntervalMs = 1000;234 };235 236#if SCUDO_CAN_USE_PRIMARY64237 template <typename Config>238 using PrimaryT = scudo::SizeClassAllocator64<Config>;239#else240 template <typename Config>241 using PrimaryT = scudo::SizeClassAllocator32<Config>;242#endif243 244 struct Secondary {245 template <typename Config>246 using CacheT = scudo::MapAllocatorNoCache<Config>;247 };248 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;249};250 251} // namespace scudo252 253#if SCUDO_FUCHSIA254#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \255 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, FuchsiaConfig)256#else257#define SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \258 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, DefaultConfig) \259 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, AndroidConfig) \260 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestConditionVariableConfig) \261 SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TestNoCacheConfig)262#endif263 264#define SCUDO_TYPED_TEST_TYPE(FIXTURE, NAME, TYPE) \265 using FIXTURE##NAME##_##TYPE = FIXTURE##NAME<scudo::TYPE>; \266 TEST_F(FIXTURE##NAME##_##TYPE, NAME) { FIXTURE##NAME<scudo::TYPE>::Run(); }267 268#define SCUDO_TYPED_TEST(FIXTURE, NAME) \269 template <class TypeParam> \270 struct FIXTURE##NAME : public FIXTURE<TypeParam> { \271 using BaseT = FIXTURE<TypeParam>; \272 void Run(); \273 }; \274 SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \275 template <class TypeParam> void FIXTURE##NAME<TypeParam>::Run()276 277// Accessing `TSD->getCache()` requires `TSD::Mutex` which isn't easy to test278// using thread-safety analysis. Alternatively, we verify the thread safety279// through a runtime check in ScopedTSD and mark the test body with280// NO_THREAD_SAFETY_ANALYSIS.281#define SCUDO_TYPED_TEST_SKIP_THREAD_SAFETY(FIXTURE, NAME) \282 template <class TypeParam> \283 struct FIXTURE##NAME : public FIXTURE<TypeParam> { \284 using BaseT = FIXTURE<TypeParam>; \285 void Run() NO_THREAD_SAFETY_ANALYSIS; \286 }; \287 SCUDO_TYPED_TEST_ALL_TYPES(FIXTURE, NAME) \288 template <class TypeParam> void FIXTURE##NAME<TypeParam>::Run()289 290SCUDO_TYPED_TEST(ScudoCombinedTest, IsOwned) {291 auto *Allocator = this->Allocator.get();292 static scudo::u8 StaticBuffer[scudo::Chunk::getHeaderSize() + 1];293 EXPECT_FALSE(294 Allocator->isOwned(&StaticBuffer[scudo::Chunk::getHeaderSize()]));295 296 scudo::u8 StackBuffer[scudo::Chunk::getHeaderSize() + 1];297 for (scudo::uptr I = 0; I < sizeof(StackBuffer); I++)298 StackBuffer[I] = 0x42U;299 EXPECT_FALSE(Allocator->isOwned(&StackBuffer[scudo::Chunk::getHeaderSize()]));300 for (scudo::uptr I = 0; I < sizeof(StackBuffer); I++)301 EXPECT_EQ(StackBuffer[I], 0x42U);302}303 304template <class Config>305void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) {306 auto *Allocator = this->Allocator.get();307 308 // This allocates and deallocates a bunch of chunks, with a wide range of309 // sizes and alignments, with a focus on sizes that could trigger weird310 // behaviors (plus or minus a small delta of a power of two for example).311 for (scudo::uptr AlignLog = MinAlignLog; AlignLog <= 16U; AlignLog++) {312 const scudo::uptr Align = 1U << AlignLog;313 for (scudo::sptr Delta = -32; Delta <= 32; Delta++) {314 if ((1LL << SizeLog) + Delta < 0)315 continue;316 const scudo::uptr Size =317 static_cast<scudo::uptr>((1LL << SizeLog) + Delta);318 void *P = Allocator->allocate(Size, Origin, Align);319 EXPECT_NE(P, nullptr);320 EXPECT_TRUE(Allocator->isOwned(P));321 EXPECT_TRUE(scudo::isAligned(reinterpret_cast<scudo::uptr>(P), Align));322 EXPECT_LE(Size, Allocator->getUsableSize(P));323 memset(P, 0xaa, Size);324 checkMemoryTaggingMaybe(Allocator, P, Size, Align);325 Allocator->deallocate(P, Origin, Size);326 }327 }328 329 if (TEST_HAS_FAILURE) {330 Allocator->printStats();331 Allocator->printFragmentationInfo();332 }333}334 335#define SCUDO_MAKE_BASIC_TEST(SizeLog) \336 SCUDO_TYPED_TEST(ScudoCombinedDeathTest, BasicCombined##SizeLog) { \337 this->BasicTest(SizeLog); \338 }339 340SCUDO_MAKE_BASIC_TEST(0)341SCUDO_MAKE_BASIC_TEST(1)342SCUDO_MAKE_BASIC_TEST(2)343SCUDO_MAKE_BASIC_TEST(3)344SCUDO_MAKE_BASIC_TEST(4)345SCUDO_MAKE_BASIC_TEST(5)346SCUDO_MAKE_BASIC_TEST(6)347SCUDO_MAKE_BASIC_TEST(7)348SCUDO_MAKE_BASIC_TEST(8)349SCUDO_MAKE_BASIC_TEST(9)350SCUDO_MAKE_BASIC_TEST(10)351SCUDO_MAKE_BASIC_TEST(11)352SCUDO_MAKE_BASIC_TEST(12)353SCUDO_MAKE_BASIC_TEST(13)354SCUDO_MAKE_BASIC_TEST(14)355SCUDO_MAKE_BASIC_TEST(15)356SCUDO_MAKE_BASIC_TEST(16)357SCUDO_MAKE_BASIC_TEST(17)358SCUDO_MAKE_BASIC_TEST(18)359SCUDO_MAKE_BASIC_TEST(19)360SCUDO_MAKE_BASIC_TEST(20)361 362SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) {363 auto *Allocator = this->Allocator.get();364 365 // Ensure that specifying ZeroContents returns a zero'd out block.366 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {367 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) {368 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U;369 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);370 EXPECT_NE(P, nullptr);371 for (scudo::uptr I = 0; I < Size; I++)372 ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');373 memset(P, 0xaa, Size);374 Allocator->deallocate(P, Origin, Size);375 }376 }377}378 379SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) {380 auto *Allocator = this->Allocator.get();381 382 // Ensure that specifying ZeroFill returns a zero'd out block.383 Allocator->setFillContents(scudo::ZeroFill);384 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {385 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) {386 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U;387 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false);388 EXPECT_NE(P, nullptr);389 for (scudo::uptr I = 0; I < Size; I++)390 ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');391 memset(P, 0xaa, Size);392 Allocator->deallocate(P, Origin, Size);393 }394 }395}396 397SCUDO_TYPED_TEST(ScudoCombinedTest, PatternOrZeroFill) {398 auto *Allocator = this->Allocator.get();399 400 // Ensure that specifying PatternOrZeroFill returns a pattern or zero filled401 // block. The primary allocator only produces pattern filled blocks if MTE402 // is disabled, so we only require pattern filled blocks in that case.403 Allocator->setFillContents(scudo::PatternOrZeroFill);404 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {405 for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) {406 const scudo::uptr Size = (1U << SizeLog) + Delta * 128U;407 void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, false);408 EXPECT_NE(P, nullptr);409 for (scudo::uptr I = 0; I < Size; I++) {410 unsigned char V = (reinterpret_cast<unsigned char *>(P))[I];411 if (isPrimaryAllocation<TestAllocator<TypeParam>>(Size,412 1U << MinAlignLog) &&413 !Allocator->useMemoryTaggingTestOnly())414 ASSERT_EQ(V, scudo::PatternFillByte);415 else416 ASSERT_TRUE(V == scudo::PatternFillByte || V == 0);417 }418 memset(P, 0xaa, Size);419 Allocator->deallocate(P, Origin, Size);420 }421 }422}423 424SCUDO_TYPED_TEST(ScudoCombinedTest, BlockReuse) {425 auto *Allocator = this->Allocator.get();426 427 // Verify that a chunk will end up being reused, at some point.428 const scudo::uptr NeedleSize = 1024U;429 void *NeedleP = Allocator->allocate(NeedleSize, Origin);430 Allocator->deallocate(NeedleP, Origin);431 bool Found = false;432 for (scudo::uptr I = 0; I < 1024U && !Found; I++) {433 void *P = Allocator->allocate(NeedleSize, Origin);434 if (Allocator->getHeaderTaggedPointer(P) ==435 Allocator->getHeaderTaggedPointer(NeedleP))436 Found = true;437 Allocator->deallocate(P, Origin);438 }439 EXPECT_TRUE(Found);440}441 442SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeIncreasing) {443 auto *Allocator = this->Allocator.get();444 445 // Reallocate a chunk all the way up to a secondary allocation, verifying that446 // we preserve the data in the process.447 scudo::uptr Size = 16;448 void *P = Allocator->allocate(Size, Origin);449 const char Marker = 'A';450 memset(P, Marker, Size);451 while (Size < TypeParam::Primary::SizeClassMap::MaxSize * 4) {452 void *NewP = Allocator->reallocate(P, Size * 2);453 EXPECT_NE(NewP, nullptr);454 for (scudo::uptr J = 0; J < Size; J++)455 EXPECT_EQ((reinterpret_cast<char *>(NewP))[J], Marker);456 memset(reinterpret_cast<char *>(NewP) + Size, Marker, Size);457 Size *= 2U;458 P = NewP;459 }460 Allocator->deallocate(P, Origin);461}462 463SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateLargeDecreasing) {464 auto *Allocator = this->Allocator.get();465 466 // Reallocate a large chunk all the way down to a byte, verifying that we467 // preserve the data in the process.468 scudo::uptr Size = TypeParam::Primary::SizeClassMap::MaxSize * 2;469 const scudo::uptr DataSize = 2048U;470 void *P = Allocator->allocate(Size, Origin);471 const char Marker = 'A';472 memset(P, Marker, scudo::Min(Size, DataSize));473 while (Size > 1U) {474 Size /= 2U;475 void *NewP = Allocator->reallocate(P, Size);476 EXPECT_NE(NewP, nullptr);477 for (scudo::uptr J = 0; J < scudo::Min(Size, DataSize); J++)478 EXPECT_EQ((reinterpret_cast<char *>(NewP))[J], Marker);479 P = NewP;480 }481 Allocator->deallocate(P, Origin);482}483 484SCUDO_TYPED_TEST(ScudoCombinedDeathTest, ReallocateSame) {485 auto *Allocator = this->Allocator.get();486 487 // Check that reallocating a chunk to a slightly smaller or larger size488 // returns the same chunk. This requires that all the sizes we iterate on use489 // the same block size, but that should be the case for MaxSize - 64 with our490 // default class size maps.491 constexpr scudo::uptr InitialSize =492 TypeParam::Primary::SizeClassMap::MaxSize - 64;493 const char Marker = 'A';494 Allocator->setFillContents(scudo::PatternOrZeroFill);495 496 void *P = Allocator->allocate(InitialSize, Origin);497 scudo::uptr CurrentSize = InitialSize;498 for (scudo::sptr Delta = -32; Delta < 32; Delta += 8) {499 memset(P, Marker, CurrentSize);500 const scudo::uptr NewSize =501 static_cast<scudo::uptr>(static_cast<scudo::sptr>(InitialSize) + Delta);502 void *NewP = Allocator->reallocate(P, NewSize);503 EXPECT_EQ(NewP, P);504 505 // Verify that existing contents have been preserved.506 for (scudo::uptr I = 0; I < scudo::Min(CurrentSize, NewSize); I++)507 EXPECT_EQ((reinterpret_cast<char *>(NewP))[I], Marker);508 509 // Verify that new bytes are set according to FillContentsMode.510 for (scudo::uptr I = CurrentSize; I < NewSize; I++) {511 unsigned char V = (reinterpret_cast<unsigned char *>(NewP))[I];512 EXPECT_TRUE(V == scudo::PatternFillByte || V == 0);513 }514 515 checkMemoryTaggingMaybe(Allocator, NewP, NewSize, 0);516 CurrentSize = NewSize;517 }518 Allocator->deallocate(P, Origin);519}520 521SCUDO_TYPED_TEST(ScudoCombinedTest, IterateOverChunks) {522 auto *Allocator = this->Allocator.get();523 // Allocates a bunch of chunks, then iterate over all the chunks, ensuring524 // they are the ones we allocated. This requires the allocator to not have any525 // other allocated chunk at this point.526 std::vector<void *> V;527 for (scudo::uptr I = 0; I < 64U; I++)528 V.push_back(Allocator->allocate(529 static_cast<scudo::uptr>(std::rand()) %530 (TypeParam::Primary::SizeClassMap::MaxSize / 2U),531 Origin));532 Allocator->disable();533 Allocator->iterateOverChunks(534 0U, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),535 [](uintptr_t Base, UNUSED size_t Size, void *Arg) {536 std::vector<void *> *V = reinterpret_cast<std::vector<void *> *>(Arg);537 void *P = reinterpret_cast<void *>(Base);538 EXPECT_NE(std::find(V->begin(), V->end(), P), V->end());539 },540 reinterpret_cast<void *>(&V));541 Allocator->enable();542 for (auto P : V)543 Allocator->deallocate(P, Origin);544}545 546SCUDO_TYPED_TEST(ScudoCombinedDeathTest, UseAfterFree) {547 auto *Allocator = this->Allocator.get();548 549 // Check that use-after-free is detected.550 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {551 const scudo::uptr Size = 1U << SizeLog;552 if (!Allocator->useMemoryTaggingTestOnly())553 continue;554 EXPECT_DEATH(555 {556 disableDebuggerdMaybe();557 void *P = Allocator->allocate(Size, Origin);558 Allocator->deallocate(P, Origin);559 reinterpret_cast<char *>(P)[0] = 'A';560 },561 "");562 EXPECT_DEATH(563 {564 disableDebuggerdMaybe();565 void *P = Allocator->allocate(Size, Origin);566 Allocator->deallocate(P, Origin);567 reinterpret_cast<char *>(P)[Size - 1] = 'A';568 },569 "");570 }571}572 573SCUDO_TYPED_TEST(ScudoCombinedDeathTest, DoubleFreeFromPrimary) {574 auto *Allocator = this->Allocator.get();575 576 for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {577 const scudo::uptr Size = 1U << SizeLog;578 if (!isPrimaryAllocation<TestAllocator<TypeParam>>(Size, 0))579 break;580 581 // Verify that a double free results in a chunk state error.582 EXPECT_DEATH(583 {584 // Allocate from primary585 void *P = Allocator->allocate(Size, Origin);586 ASSERT_TRUE(P != nullptr);587 Allocator->deallocate(P, Origin);588 Allocator->deallocate(P, Origin);589 },590 "invalid chunk state");591 }592}593 594SCUDO_TYPED_TEST(ScudoCombinedDeathTest, DisableMemoryTagging) {595 auto *Allocator = this->Allocator.get();596 597 if (Allocator->useMemoryTaggingTestOnly()) {598 // Check that disabling memory tagging works correctly.599 void *P = Allocator->allocate(2048, Origin);600 EXPECT_DEATH(reinterpret_cast<char *>(P)[2048] = 'A', "");601 scudo::ScopedDisableMemoryTagChecks NoTagChecks;602 Allocator->disableMemoryTagging();603 reinterpret_cast<char *>(P)[2048] = 'A';604 Allocator->deallocate(P, Origin);605 606 P = Allocator->allocate(2048, Origin);607 EXPECT_EQ(scudo::untagPointer(P), P);608 reinterpret_cast<char *>(P)[2048] = 'A';609 Allocator->deallocate(P, Origin);610 611 Allocator->releaseToOS(scudo::ReleaseToOS::Force);612 }613}614 615SCUDO_TYPED_TEST(ScudoCombinedTest, Stats) {616 auto *Allocator = this->Allocator.get();617 618 std::string Stats(10000, '\0');619 scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());620 if (ActualSize > Stats.size()) {621 Stats.resize(ActualSize);622 ActualSize = Allocator->getStats(Stats.data(), Stats.size());623 }624 EXPECT_GE(Stats.size(), ActualSize);625 626 // Basic checks on the contents of the statistics output, which also allows us627 // to verify that we got it all.628 EXPECT_NE(Stats.find("Stats: SizeClassAllocator"), std::string::npos);629 EXPECT_NE(Stats.find("Stats: MapAllocator"), std::string::npos);630 // Do not explicitly check for quarantine stats since a config can disable631 // them. Other tests verify this (QuarantineEnabled/QuarantineDisabled).632}633 634SCUDO_TYPED_TEST_SKIP_THREAD_SAFETY(ScudoCombinedTest, Drain) {635 using AllocatorT = typename BaseT::AllocatorT;636 auto *Allocator = this->Allocator.get();637 638 std::vector<void *> V;639 for (scudo::uptr I = 0; I < 64U; I++)640 V.push_back(Allocator->allocate(641 static_cast<scudo::uptr>(std::rand()) %642 (TypeParam::Primary::SizeClassMap::MaxSize / 2U),643 Origin));644 for (auto P : V)645 Allocator->deallocate(P, Origin);646 647 typename AllocatorT::TSDRegistryT::ScopedTSD TSD(648 *Allocator->getTSDRegistry());649 EXPECT_TRUE(!TSD->getSizeClassAllocator().isEmpty());650 TSD->getSizeClassAllocator().drain();651 EXPECT_TRUE(TSD->getSizeClassAllocator().isEmpty());652}653 654SCUDO_TYPED_TEST_SKIP_THREAD_SAFETY(ScudoCombinedTest, ForceCacheDrain) {655 using AllocatorT = typename BaseT::AllocatorT;656 auto *Allocator = this->Allocator.get();657 658 std::vector<void *> V;659 for (scudo::uptr I = 0; I < 64U; I++)660 V.push_back(Allocator->allocate(661 static_cast<scudo::uptr>(std::rand()) %662 (TypeParam::Primary::SizeClassMap::MaxSize / 2U),663 Origin));664 for (auto P : V)665 Allocator->deallocate(P, Origin);666 667 // `ForceAll` will also drain the caches.668 Allocator->releaseToOS(scudo::ReleaseToOS::ForceAll);669 670 typename AllocatorT::TSDRegistryT::ScopedTSD TSD(671 *Allocator->getTSDRegistry());672 EXPECT_TRUE(TSD->getSizeClassAllocator().isEmpty());673 EXPECT_EQ(TSD->getQuarantineCache().getSize(), 0U);674 EXPECT_TRUE(Allocator->getQuarantine()->isEmpty());675}676 677SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) {678 std::mutex Mutex;679 std::condition_variable Cv;680 bool Ready = false;681 auto *Allocator = this->Allocator.get();682 std::thread Threads[32];683 for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)684 Threads[I] = std::thread([&]() {685 {686 std::unique_lock<std::mutex> Lock(Mutex);687 while (!Ready)688 Cv.wait(Lock);689 }690 std::vector<std::pair<void *, scudo::uptr>> V;691 for (scudo::uptr I = 0; I < 256U; I++) {692 const scudo::uptr Size = static_cast<scudo::uptr>(std::rand()) % 4096U;693 void *P = Allocator->allocate(Size, Origin);694 // A region could have ran out of memory, resulting in a null P.695 if (P)696 V.push_back(std::make_pair(P, Size));697 }698 699 // Try to interleave pushBlocks(), popBatch() and releaseToOS().700 Allocator->releaseToOS(scudo::ReleaseToOS::Force);701 702 while (!V.empty()) {703 auto Pair = V.back();704 Allocator->deallocate(Pair.first, Origin, Pair.second);705 V.pop_back();706 }707 });708 {709 std::unique_lock<std::mutex> Lock(Mutex);710 Ready = true;711 Cv.notify_all();712 }713 for (auto &T : Threads)714 T.join();715 Allocator->releaseToOS(scudo::ReleaseToOS::Force);716}717 718// Test that multiple instantiations of the allocator have not messed up the719// process's signal handlers (GWP-ASan used to do this).720TEST(ScudoCombinedDeathTest, SKIP_ON_FUCHSIA(testSEGV)) {721 const scudo::uptr Size = 4 * scudo::getPageSizeCached();722 scudo::ReservedMemoryT ReservedMemory;723 ASSERT_TRUE(ReservedMemory.create(/*Addr=*/0U, Size, "testSEGV"));724 void *P = reinterpret_cast<void *>(ReservedMemory.getBase());725 ASSERT_NE(P, nullptr);726 EXPECT_DEATH(memset(P, 0xaa, Size), "");727 ReservedMemory.release();728}729 730struct DeathSizeClassConfig {731 static const scudo::uptr NumBits = 1;732 static const scudo::uptr MinSizeLog = 10;733 static const scudo::uptr MidSizeLog = 10;734 static const scudo::uptr MaxSizeLog = 13;735 static const scudo::u16 MaxNumCachedHint = 8;736 static const scudo::uptr MaxBytesCachedLog = 12;737 static const scudo::uptr SizeDelta = 0;738};739 740static const scudo::uptr DeathRegionSizeLog = 21U;741struct DeathConfig {742 static const bool MaySupportMemoryTagging = false;743 template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;744 745 struct Primary {746 // Tiny allocator, its Primary only serves chunks of four sizes.747 using SizeClassMap = scudo::FixedSizeClassMap<DeathSizeClassConfig>;748 static const scudo::uptr RegionSizeLog = DeathRegionSizeLog;749 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;750 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;751 typedef scudo::uptr CompactPtrT;752 static const scudo::uptr CompactPtrScale = 0;753 static const bool EnableRandomOffset = true;754 static const scudo::uptr MapSizeIncrement = 1UL << 18;755 static const scudo::uptr GroupSizeLog = 18;756 };757 template <typename Config>758 using PrimaryT = scudo::SizeClassAllocator64<Config>;759 760 struct Secondary {761 template <typename Config>762 using CacheT = scudo::MapAllocatorNoCache<Config>;763 };764 765 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;766};767 768TEST(ScudoCombinedDeathTest, DeathCombined) {769 using AllocatorT = TestAllocator<DeathConfig>;770 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());771 772 const scudo::uptr Size = 1000U;773 void *P = Allocator->allocate(Size, Origin);774 EXPECT_NE(P, nullptr);775 776 // Invalid sized deallocation.777 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");778 779 // Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.780 UNUSED void *MisalignedP =781 reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);782 EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");783 EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");784 785 // Header corruption.786 scudo::u64 *H =787 reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P));788 *H ^= 0x42U;789 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");790 *H ^= 0x420042U;791 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");792 *H ^= 0x420000U;793 794 // Invalid chunk state.795 Allocator->deallocate(P, Origin, Size);796 EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");797 EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), "");798 EXPECT_DEATH(Allocator->getUsableSize(P), "");799}800 801// Verify that when a region gets full, the allocator will still manage to802// fulfill the allocation through a larger size class.803TEST(ScudoCombinedTest, FullRegion) {804 using AllocatorT = TestAllocator<DeathConfig>;805 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());806 807 std::vector<void *> V;808 scudo::uptr FailedAllocationsCount = 0;809 for (scudo::uptr ClassId = 1U;810 ClassId <= DeathConfig::Primary::SizeClassMap::LargestClassId;811 ClassId++) {812 const scudo::uptr Size =813 DeathConfig::Primary::SizeClassMap::getSizeByClassId(ClassId);814 // Allocate enough to fill all of the regions above this one.815 const scudo::uptr MaxNumberOfChunks =816 ((1U << DeathRegionSizeLog) / Size) *817 (DeathConfig::Primary::SizeClassMap::LargestClassId - ClassId + 1);818 void *P;819 for (scudo::uptr I = 0; I <= MaxNumberOfChunks; I++) {820 P = Allocator->allocate(Size - 64U, Origin);821 if (!P)822 FailedAllocationsCount++;823 else824 V.push_back(P);825 }826 while (!V.empty()) {827 Allocator->deallocate(V.back(), Origin);828 V.pop_back();829 }830 }831 EXPECT_EQ(FailedAllocationsCount, 0U);832}833 834// Ensure that releaseToOS can be called prior to any other allocator835// operation without issue.836SCUDO_TYPED_TEST(ScudoCombinedTest, ReleaseToOS) {837 auto *Allocator = this->Allocator.get();838 Allocator->releaseToOS(scudo::ReleaseToOS::Force);839}840 841SCUDO_TYPED_TEST(ScudoCombinedTest, OddEven) {842 auto *Allocator = this->Allocator.get();843 Allocator->setOption(scudo::Option::MemtagTuning, M_MEMTAG_TUNING_BUFFER_OVERFLOW);844 845 if (!Allocator->useMemoryTaggingTestOnly())846 return;847 848 auto CheckOddEven = [](scudo::uptr P1, scudo::uptr P2) {849 scudo::uptr Tag1 = scudo::extractTag(scudo::loadTag(P1));850 scudo::uptr Tag2 = scudo::extractTag(scudo::loadTag(P2));851 EXPECT_NE(Tag1 % 2, Tag2 % 2);852 };853 854 using SizeClassMap = typename TypeParam::Primary::SizeClassMap;855 for (scudo::uptr ClassId = 1U; ClassId <= SizeClassMap::LargestClassId;856 ClassId++) {857 const scudo::uptr Size = SizeClassMap::getSizeByClassId(ClassId);858 859 std::set<scudo::uptr> Ptrs;860 bool Found = false;861 for (unsigned I = 0; I != 65536; ++I) {862 scudo::uptr P = scudo::untagPointer(reinterpret_cast<scudo::uptr>(863 Allocator->allocate(Size - scudo::Chunk::getHeaderSize(), Origin)));864 if (Ptrs.count(P - Size)) {865 Found = true;866 CheckOddEven(P, P - Size);867 break;868 }869 if (Ptrs.count(P + Size)) {870 Found = true;871 CheckOddEven(P, P + Size);872 break;873 }874 Ptrs.insert(P);875 }876 EXPECT_TRUE(Found);877 }878}879 880SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) {881 auto *Allocator = this->Allocator.get();882 883 std::vector<void *> Ptrs(65536);884 885 Allocator->setOption(scudo::Option::ThreadDisableMemInit, 1);886 887 constexpr scudo::uptr MinAlignLog = FIRST_32_SECOND_64(3U, 4U);888 889 // Test that if mem-init is disabled on a thread, calloc should still work as890 // expected. This is tricky to ensure when MTE is enabled, so this test tries891 // to exercise the relevant code on our MTE path.892 for (scudo::uptr ClassId = 1U; ClassId <= 8; ClassId++) {893 using SizeClassMap = typename TypeParam::Primary::SizeClassMap;894 const scudo::uptr Size =895 SizeClassMap::getSizeByClassId(ClassId) - scudo::Chunk::getHeaderSize();896 if (Size < 8)897 continue;898 for (unsigned I = 0; I != Ptrs.size(); ++I) {899 Ptrs[I] = Allocator->allocate(Size, Origin);900 memset(Ptrs[I], 0xaa, Size);901 }902 for (unsigned I = 0; I != Ptrs.size(); ++I)903 Allocator->deallocate(Ptrs[I], Origin, Size);904 for (unsigned I = 0; I != Ptrs.size(); ++I) {905 Ptrs[I] = Allocator->allocate(Size - 8, Origin);906 memset(Ptrs[I], 0xbb, Size - 8);907 }908 for (unsigned I = 0; I != Ptrs.size(); ++I)909 Allocator->deallocate(Ptrs[I], Origin, Size - 8);910 for (unsigned I = 0; I != Ptrs.size(); ++I) {911 Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);912 for (scudo::uptr J = 0; J < Size; ++J)913 ASSERT_EQ((reinterpret_cast<char *>(Ptrs[I]))[J], '\0');914 }915 }916 917 Allocator->setOption(scudo::Option::ThreadDisableMemInit, 0);918}919 920SCUDO_TYPED_TEST(ScudoCombinedTest, ReallocateInPlaceStress) {921 auto *Allocator = this->Allocator.get();922 923 // Regression test: make realloc-in-place happen at the very right end of a924 // mapped region.925 constexpr size_t nPtrs = 10000;926 for (scudo::uptr i = 1; i < 32; ++i) {927 scudo::uptr Size = 16 * i - 1;928 std::vector<void *> Ptrs;929 for (size_t i = 0; i < nPtrs; ++i) {930 void *P = Allocator->allocate(Size, Origin);931 P = Allocator->reallocate(P, Size + 1);932 Ptrs.push_back(P);933 }934 935 for (size_t i = 0; i < nPtrs; ++i)936 Allocator->deallocate(Ptrs[i], Origin);937 }938}939 940SCUDO_TYPED_TEST(ScudoCombinedTest, RingBufferDefaultDisabled) {941 // The RingBuffer is not initialized until tracking is enabled for the942 // first time.943 auto *Allocator = this->Allocator.get();944 EXPECT_EQ(0u, Allocator->getRingBufferSize());945 EXPECT_EQ(nullptr, Allocator->getRingBufferAddress());946}947 948SCUDO_TYPED_TEST(ScudoCombinedTest, RingBufferInitOnce) {949 auto *Allocator = this->Allocator.get();950 Allocator->setTrackAllocationStacks(true);951 952 auto RingBufferSize = Allocator->getRingBufferSize();953 ASSERT_GT(RingBufferSize, 0u);954 auto *RingBufferAddress = Allocator->getRingBufferAddress();955 EXPECT_NE(nullptr, RingBufferAddress);956 957 // Enable tracking again to verify that the initialization only happens once.958 Allocator->setTrackAllocationStacks(true);959 ASSERT_EQ(RingBufferSize, Allocator->getRingBufferSize());960 EXPECT_EQ(RingBufferAddress, Allocator->getRingBufferAddress());961}962 963SCUDO_TYPED_TEST(ScudoCombinedTest, RingBufferSize) {964 auto *Allocator = this->Allocator.get();965 Allocator->setTrackAllocationStacks(true);966 967 auto RingBufferSize = Allocator->getRingBufferSize();968 ASSERT_GT(RingBufferSize, 0u);969 EXPECT_EQ(Allocator->getRingBufferAddress()[RingBufferSize - 1], '\0');970}971 972SCUDO_TYPED_TEST(ScudoCombinedTest, RingBufferAddress) {973 auto *Allocator = this->Allocator.get();974 Allocator->setTrackAllocationStacks(true);975 976 auto *RingBufferAddress = Allocator->getRingBufferAddress();977 EXPECT_NE(RingBufferAddress, nullptr);978 EXPECT_EQ(RingBufferAddress, Allocator->getRingBufferAddress());979}980 981SCUDO_TYPED_TEST(ScudoCombinedTest, StackDepotDefaultDisabled) {982 // The StackDepot is not initialized until tracking is enabled for the983 // first time.984 auto *Allocator = this->Allocator.get();985 EXPECT_EQ(0u, Allocator->getStackDepotSize());986 EXPECT_EQ(nullptr, Allocator->getStackDepotAddress());987}988 989SCUDO_TYPED_TEST(ScudoCombinedTest, StackDepotInitOnce) {990 auto *Allocator = this->Allocator.get();991 Allocator->setTrackAllocationStacks(true);992 993 auto StackDepotSize = Allocator->getStackDepotSize();994 EXPECT_GT(StackDepotSize, 0u);995 auto *StackDepotAddress = Allocator->getStackDepotAddress();996 EXPECT_NE(nullptr, StackDepotAddress);997 998 // Enable tracking again to verify that the initialization only happens once.999 Allocator->setTrackAllocationStacks(true);1000 EXPECT_EQ(StackDepotSize, Allocator->getStackDepotSize());1001 EXPECT_EQ(StackDepotAddress, Allocator->getStackDepotAddress());1002}1003 1004SCUDO_TYPED_TEST(ScudoCombinedTest, StackDepotSize) {1005 auto *Allocator = this->Allocator.get();1006 Allocator->setTrackAllocationStacks(true);1007 1008 auto StackDepotSize = Allocator->getStackDepotSize();1009 EXPECT_GT(StackDepotSize, 0u);1010 EXPECT_EQ(Allocator->getStackDepotAddress()[StackDepotSize - 1], '\0');1011}1012 1013SCUDO_TYPED_TEST(ScudoCombinedTest, StackDepotAddress) {1014 auto *Allocator = this->Allocator.get();1015 Allocator->setTrackAllocationStacks(true);1016 1017 auto *StackDepotAddress = Allocator->getStackDepotAddress();1018 EXPECT_NE(StackDepotAddress, nullptr);1019 EXPECT_EQ(StackDepotAddress, Allocator->getStackDepotAddress());1020}1021 1022SCUDO_TYPED_TEST(ScudoCombinedTest, StackDepot) {1023 alignas(scudo::StackDepot) char Buf[sizeof(scudo::StackDepot) +1024 1024 * sizeof(scudo::atomic_u64) +1025 1024 * sizeof(scudo::atomic_u32)] = {};1026 auto *Depot = reinterpret_cast<scudo::StackDepot *>(Buf);1027 Depot->init(1024, 1024);1028 ASSERT_TRUE(Depot->isValid(sizeof(Buf)));1029 ASSERT_FALSE(Depot->isValid(sizeof(Buf) - 1));1030 scudo::uptr Stack[] = {1, 2, 3};1031 scudo::u32 Elem = Depot->insert(&Stack[0], &Stack[3]);1032 scudo::uptr RingPosPtr = 0;1033 scudo::uptr SizePtr = 0;1034 ASSERT_TRUE(Depot->find(Elem, &RingPosPtr, &SizePtr));1035 ASSERT_EQ(SizePtr, 3u);1036 EXPECT_EQ(Depot->at(RingPosPtr), 1u);1037 EXPECT_EQ(Depot->at(RingPosPtr + 1), 2u);1038 EXPECT_EQ(Depot->at(RingPosPtr + 2), 3u);1039}1040 1041#if SCUDO_CAN_USE_PRIMARY641042#if SCUDO_TRUSTY1043 1044// TrustyConfig is designed for a domain-specific allocator. Add a basic test1045// which covers only simple operations and ensure the configuration is able to1046// compile.1047TEST(ScudoCombinedTest, BasicTrustyConfig) {1048 using AllocatorT = TestAllocator<scudo::TrustyConfig>;1049 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1050 1051 for (scudo::uptr ClassId = 1U;1052 ClassId <= scudo::TrustyConfig::SizeClassMap::LargestClassId;1053 ClassId++) {1054 const scudo::uptr Size =1055 scudo::TrustyConfig::SizeClassMap::getSizeByClassId(ClassId);1056 void *p = Allocator->allocate(Size - scudo::Chunk::getHeaderSize(), Origin);1057 ASSERT_NE(p, nullptr);1058 free(p);1059 }1060 1061 bool UnlockRequired;1062 typename AllocatorT::TSDRegistryT::ScopedTSD TSD(1063 *Allocator->getTSDRegistry());1064 TSD->getSizeClassAllocator().drain();1065 1066 Allocator->releaseToOS(scudo::ReleaseToOS::Force);1067}1068 1069#endif1070#endif1071 1072struct TestQuarantineSizeClassConfig {1073 static const scudo::uptr NumBits = 1;1074 static const scudo::uptr MinSizeLog = 10;1075 static const scudo::uptr MidSizeLog = 10;1076 static const scudo::uptr MaxSizeLog = 13;1077 static const scudo::u16 MaxNumCachedHint = 8;1078 static const scudo::uptr MaxBytesCachedLog = 12;1079 static const scudo::uptr SizeDelta = 0;1080};1081 1082struct TestQuarantineConfig {1083 static const bool MaySupportMemoryTagging = false;1084 1085 template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;1086 1087 struct Primary {1088 // Tiny allocator, its Primary only serves chunks of four sizes.1089 using SizeClassMap = scudo::FixedSizeClassMap<DeathSizeClassConfig>;1090 static const scudo::uptr RegionSizeLog = DeathRegionSizeLog;1091 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;1092 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;1093 typedef scudo::uptr CompactPtrT;1094 static const scudo::uptr CompactPtrScale = 0;1095 static const bool EnableRandomOffset = true;1096 static const scudo::uptr MapSizeIncrement = 1UL << 18;1097 static const scudo::uptr GroupSizeLog = 18;1098 };1099 template <typename Config>1100 using PrimaryT = scudo::SizeClassAllocator64<Config>;1101 1102 struct Secondary {1103 template <typename Config>1104 using CacheT = scudo::MapAllocatorNoCache<Config>;1105 };1106 1107 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;1108};1109 1110// Verify that the quarantine exists by default.1111TEST(ScudoCombinedTest, QuarantineEnabled) {1112 using AllocatorT = TestAllocator<TestQuarantineConfig>;1113 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1114 1115 const scudo::uptr Size = 1000U;1116 void *P = Allocator->allocate(Size, Origin);1117 EXPECT_NE(P, nullptr);1118 Allocator->deallocate(P, Origin);1119 1120 std::string Stats(10000, '\0');1121 scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());1122 if (ActualSize > Stats.size()) {1123 Stats.resize(ActualSize);1124 ActualSize = Allocator->getStats(Stats.data(), Stats.size());1125 }1126 EXPECT_GE(Stats.size(), ActualSize);1127 1128 // Quarantine stats should be present.1129 EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos);1130}1131 1132struct TestQuarantineDisabledConfig : TestQuarantineConfig {1133 static const bool QuarantineDisabled = true;1134};1135 1136TEST(ScudoCombinedTest, QuarantineDisabled) {1137 using AllocatorT = TestAllocator<TestQuarantineDisabledConfig>;1138 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1139 1140 const scudo::uptr Size = 1000U;1141 void *P = Allocator->allocate(Size, Origin);1142 EXPECT_NE(P, nullptr);1143 Allocator->deallocate(P, Origin);1144 1145 std::string Stats(10000, '\0');1146 scudo::uptr ActualSize = Allocator->getStats(Stats.data(), Stats.size());1147 if (ActualSize > Stats.size()) {1148 Stats.resize(ActualSize);1149 ActualSize = Allocator->getStats(Stats.data(), Stats.size());1150 }1151 EXPECT_GE(Stats.size(), ActualSize);1152 1153 // No quarantine stats should not be present.1154 EXPECT_EQ(Stats.find("Stats: Quarantine"), std::string::npos);1155}1156 1157struct UsableSizeClassConfig {1158 static const scudo::uptr NumBits = 1;1159 static const scudo::uptr MinSizeLog = 10;1160 static const scudo::uptr MidSizeLog = 10;1161 static const scudo::uptr MaxSizeLog = 13;1162 static const scudo::u16 MaxNumCachedHint = 8;1163 static const scudo::uptr MaxBytesCachedLog = 12;1164 static const scudo::uptr SizeDelta = 0;1165};1166 1167struct TestExactUsableSizeConfig {1168 static const bool MaySupportMemoryTagging = false;1169 static const bool QuarantineDisabled = true;1170 1171 template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;1172 1173 struct Primary {1174 // In order to properly test the usable size, this Primary config has1175 // four real size classes: 1024, 2048, 4096, 8192.1176 using SizeClassMap = scudo::FixedSizeClassMap<UsableSizeClassConfig>;1177 static const scudo::uptr RegionSizeLog = 21U;1178 static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;1179 static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;1180 typedef scudo::uptr CompactPtrT;1181 static const scudo::uptr CompactPtrScale = 0;1182 static const bool EnableRandomOffset = true;1183 static const scudo::uptr MapSizeIncrement = 1UL << 18;1184 static const scudo::uptr GroupSizeLog = 18;1185 };1186 template <typename Config>1187 using PrimaryT = scudo::SizeClassAllocator64<Config>;1188 1189 struct Secondary {1190 template <typename Config>1191 using CacheT = scudo::MapAllocatorNoCache<Config>;1192 };1193 1194 template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;1195};1196 1197template <class AllocatorT> void VerifyExactUsableSize(AllocatorT &Allocator) {1198 // Scan through all sizes up to 10000 then some larger sizes.1199 for (scudo::uptr Size = 1; Size < 10000; Size++) {1200 void *P = Allocator.allocate(Size, Origin);1201 EXPECT_EQ(Size, Allocator.getUsableSize(P))1202 << "Failed usable size at allocation size " << Size;1203 Allocator.deallocate(P, Origin);1204 }1205 1206 // Verify that aligned allocations also return the exact size allocated.1207 const scudo::uptr AllocSize = 313;1208 for (scudo::uptr Align = 1; Align <= 8; Align++) {1209 void *P = Allocator.allocate(AllocSize, Origin, 1U << Align);1210 EXPECT_EQ(AllocSize, Allocator.getUsableSize(P))1211 << "Failed usable size at allocation size " << AllocSize << " at align "1212 << 1 << Align;1213 Allocator.deallocate(P, Origin);1214 }1215 1216 // Verify an explicitly large allocations.1217 const scudo::uptr LargeAllocSize = 1000000;1218 void *P = Allocator.allocate(LargeAllocSize, Origin);1219 EXPECT_EQ(LargeAllocSize, Allocator.getUsableSize(P));1220 Allocator.deallocate(P, Origin);1221 1222 // Now do it for aligned allocations for large allocations.1223 for (scudo::uptr Align = 1; Align <= 8; Align++) {1224 void *P = Allocator.allocate(LargeAllocSize, Origin, 1U << Align);1225 EXPECT_EQ(LargeAllocSize, Allocator.getUsableSize(P))1226 << "Failed usable size at allocation size " << AllocSize << " at align "1227 << 1 << Align;1228 Allocator.deallocate(P, Origin);1229 }1230}1231 1232template <class AllocatorT>1233void VerifyIterateOverUsableSize(AllocatorT &Allocator) {1234 // This will not verify if the size is the exact size or the size of the1235 // size class. Instead verify that the size matches the usable size and1236 // assume the other tests have verified getUsableSize.1237 std::unordered_map<void *, size_t> Pointers;1238 Pointers.insert({Allocator.allocate(128, Origin), 0U});1239 Pointers.insert({Allocator.allocate(128, Origin, 32), 0U});1240 Pointers.insert({Allocator.allocate(2000, Origin), 0U});1241 Pointers.insert({Allocator.allocate(2000, Origin, 64), 0U});1242 Pointers.insert({Allocator.allocate(8000, Origin), 0U});1243 Pointers.insert({Allocator.allocate(8000, Origin, 128), 0U});1244 Pointers.insert({Allocator.allocate(2000205, Origin), 0U});1245 Pointers.insert({Allocator.allocate(2000205, Origin, 128), 0U});1246 Pointers.insert({Allocator.allocate(2000205, Origin, 256), 0U});1247 1248 Allocator.disable();1249 Allocator.iterateOverChunks(1250 0, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),1251 [](uintptr_t Base, size_t Size, void *Arg) {1252 std::unordered_map<void *, size_t> *Pointers =1253 reinterpret_cast<std::unordered_map<void *, size_t> *>(Arg);1254 (*Pointers)[reinterpret_cast<void *>(Base)] = Size;1255 },1256 reinterpret_cast<void *>(&Pointers));1257 Allocator.enable();1258 1259 for (auto [Ptr, IterateSize] : Pointers) {1260 EXPECT_NE(0U, IterateSize)1261 << "Pointer " << Ptr << " not found in iterateOverChunks call.";1262 EXPECT_EQ(IterateSize, Allocator.getUsableSize(Ptr))1263 << "Pointer " << Ptr1264 << " mismatch between iterate size and usable size.";1265 Allocator.deallocate(Ptr, Origin);1266 }1267}1268 1269TEST(ScudoCombinedTest, ExactUsableSize) {1270 using AllocatorT = scudo::Allocator<TestExactUsableSizeConfig>;1271 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1272 1273 VerifyExactUsableSize<AllocatorT>(*Allocator);1274 VerifyIterateOverUsableSize<AllocatorT>(*Allocator);1275}1276 1277struct TestExactUsableSizeMTEConfig : TestExactUsableSizeConfig {1278 static const bool MaySupportMemoryTagging = true;1279};1280 1281TEST(ScudoCombinedTest, ExactUsableSizeMTE) {1282 if (!scudo::archSupportsMemoryTagging() ||1283 !scudo::systemDetectsMemoryTagFaultsTestOnly())1284 TEST_SKIP("Only supported on systems that can enable MTE.");1285 1286 scudo::enableSystemMemoryTaggingTestOnly();1287 1288 using AllocatorT = scudo::Allocator<TestExactUsableSizeMTEConfig>;1289 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1290 1291 VerifyExactUsableSize<AllocatorT>(*Allocator);1292 VerifyIterateOverUsableSize<AllocatorT>(*Allocator);1293}1294 1295template <class AllocatorT>1296void VerifyUsableSizePrimary(AllocatorT &Allocator) {1297 std::vector<scudo::uptr> SizeClasses = {1024U, 2048U, 4096U, 8192U};1298 for (size_t I = 0; I < SizeClasses.size(); I++) {1299 scudo::uptr SizeClass = SizeClasses[I];1300 scudo::uptr StartSize;1301 if (I == 0)1302 StartSize = 1;1303 else1304 StartSize = SizeClasses[I - 1];1305 scudo::uptr UsableSize = SizeClass - scudo::Chunk::getHeaderSize();1306 for (scudo::uptr Size = StartSize; Size < UsableSize; Size++) {1307 void *P = Allocator.allocate(Size, Origin);1308 EXPECT_EQ(UsableSize, Allocator.getUsableSize(P))1309 << "Failed usable size at allocation size " << Size1310 << " for size class " << SizeClass;1311 memset(P, 0xff, UsableSize);1312 EXPECT_EQ(Allocator.getBlockBeginTestOnly(P) + SizeClass,1313 reinterpret_cast<scudo::uptr>(P) + UsableSize);1314 Allocator.deallocate(P, Origin);1315 }1316 1317 StartSize = UsableSize + 1;1318 }1319 1320 std::vector<scudo::uptr> Alignments = {32U, 128U};1321 for (size_t I = 0; I < SizeClasses.size(); I++) {1322 scudo::uptr SizeClass = SizeClasses[I];1323 scudo::uptr AllocSize;1324 if (I == 0)1325 AllocSize = 1;1326 else1327 AllocSize = SizeClasses[I - 1] + 1;1328 1329 for (auto Alignment : Alignments) {1330 void *P = Allocator.allocate(AllocSize, Origin, Alignment);1331 scudo::uptr UsableSize = Allocator.getUsableSize(P);1332 memset(P, 0xff, UsableSize);1333 EXPECT_EQ(Allocator.getBlockBeginTestOnly(P) + SizeClass,1334 reinterpret_cast<scudo::uptr>(P) + UsableSize)1335 << "Failed usable size at allocation size " << AllocSize1336 << " for size class " << SizeClass << " at alignment " << Alignment;1337 Allocator.deallocate(P, Origin);1338 }1339 }1340}1341 1342template <class AllocatorT>1343void VerifyUsableSizeSecondary(AllocatorT &Allocator) {1344 const scudo::uptr LargeAllocSize = 996780;1345 const scudo::uptr PageSize = scudo::getPageSizeCached();1346 void *P = Allocator.allocate(LargeAllocSize, Origin);1347 scudo::uptr UsableSize = Allocator.getUsableSize(P);1348 memset(P, 0xff, UsableSize);1349 // Assumes that the secondary always rounds up allocations to a page boundary.1350 EXPECT_EQ(scudo::roundUp(reinterpret_cast<scudo::uptr>(P) + LargeAllocSize,1351 PageSize),1352 reinterpret_cast<scudo::uptr>(P) + UsableSize);1353 Allocator.deallocate(P, Origin);1354 1355 // Check aligned allocations now.1356 for (scudo::uptr Alignment = 1; Alignment <= 8; Alignment++) {1357 void *P = Allocator.allocate(LargeAllocSize, Origin, 1U << Alignment);1358 scudo::uptr UsableSize = Allocator.getUsableSize(P);1359 EXPECT_EQ(scudo::roundUp(reinterpret_cast<scudo::uptr>(P) + LargeAllocSize,1360 PageSize),1361 reinterpret_cast<scudo::uptr>(P) + UsableSize)1362 << "Failed usable size at allocation size " << LargeAllocSize1363 << " at alignment " << Alignment;1364 Allocator.deallocate(P, Origin);1365 }1366}1367 1368struct TestFullUsableSizeConfig : TestExactUsableSizeConfig {1369 static const bool ExactUsableSize = false;1370};1371 1372TEST(ScudoCombinedTest, FullUsableSize) {1373 using AllocatorT = scudo::Allocator<TestFullUsableSizeConfig>;1374 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1375 1376 VerifyUsableSizePrimary<AllocatorT>(*Allocator);1377 VerifyUsableSizeSecondary<AllocatorT>(*Allocator);1378 VerifyIterateOverUsableSize<AllocatorT>(*Allocator);1379}1380 1381struct TestFullUsableSizeMTEConfig : TestFullUsableSizeConfig {1382 static const bool MaySupportMemoryTagging = true;1383};1384 1385TEST(ScudoCombinedTest, FullUsableSizeMTE) {1386 if (!scudo::archSupportsMemoryTagging() ||1387 !scudo::systemDetectsMemoryTagFaultsTestOnly())1388 TEST_SKIP("Only supported on systems that can enable MTE.");1389 1390 scudo::enableSystemMemoryTaggingTestOnly();1391 1392 using AllocatorT = scudo::Allocator<TestFullUsableSizeMTEConfig>;1393 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1394 1395 // When MTE is enabled, you get exact sizes.1396 VerifyExactUsableSize<AllocatorT>(*Allocator);1397 VerifyIterateOverUsableSize<AllocatorT>(*Allocator);1398}1399// Verify that no special quarantine blocks appear in iterateOverChunks.1400TEST(ScudoCombinedTest, QuarantineIterateOverChunks) {1401 using AllocatorT = TestAllocator<TestQuarantineConfig>;1402 auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());1403 1404 // Do a bunch of allocations and deallocations. At the end there should1405 // be no special quarantine blocks in our callbacks, and no blocks at all.1406 std::vector<scudo::uptr> Sizes = {128, 128, 256, 256};1407 for (auto const Size : Sizes) {1408 void *Ptr = Allocator->allocate(Size, Origin);1409 EXPECT_NE(Ptr, nullptr);1410 Allocator->deallocate(Ptr, Origin);1411 }1412 std::unordered_map<uintptr_t, size_t> Pointers;1413 Allocator->disable();1414 Allocator->iterateOverChunks(1415 0, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),1416 [](uintptr_t Base, size_t Size, void *Arg) {1417 std::unordered_map<uintptr_t, size_t> *Pointers =1418 reinterpret_cast<std::unordered_map<uintptr_t, size_t> *>(Arg);1419 (*Pointers)[Base] = Size;1420 },1421 reinterpret_cast<void *>(&Pointers));1422 Allocator->enable();1423 1424 for (const auto [Base, Size] : Pointers) {1425 EXPECT_TRUE(false) << "Unexpected pointer found in iterateOverChunks "1426 << std::hex << Base << " Size " << std::dec << Size;1427 }1428}1429