brintos

brintos / llvm-project-archived public Read only

0
0
Text · 51.3 KiB · 1d4208b Raw
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