1 //===-- recoverable.cpp -----------------------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include <atomic> 10 #include <mutex> 11 #include <regex> 12 #include <string> 13 #include <thread> 14 #include <vector> 15 16 #include "gwp_asan/common.h" 17 #include "gwp_asan/crash_handler.h" 18 #include "gwp_asan/tests/harness.h" 19 20 void CheckOnlyOneGwpAsanCrash(const std::string &OutputBuffer) { 21 const char *kGwpAsanErrorString = "GWP-ASan detected a memory error"; 22 size_t FirstIndex = OutputBuffer.find(kGwpAsanErrorString); 23 ASSERT_NE(FirstIndex, std::string::npos) << "Didn't detect a GWP-ASan crash"; 24 ASSERT_EQ(OutputBuffer.find(kGwpAsanErrorString, FirstIndex + 1), 25 std::string::npos) 26 << "Detected more than one GWP-ASan crash:\n" 27 << OutputBuffer; 28 } 29 30 TEST_P(BacktraceGuardedPoolAllocator, MultipleDoubleFreeOnlyOneOutput) { 31 SCOPED_TRACE(""); 32 void *Ptr = AllocateMemory(GPA); 33 DeallocateMemory(GPA, Ptr); 34 // First time should generate a crash report. 35 DeallocateMemory(GPA, Ptr); 36 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 37 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); 38 39 // Ensure the crash is only reported once. 40 GetOutputBuffer().clear(); 41 for (size_t i = 0; i < 100; ++i) { 42 DeallocateMemory(GPA, Ptr); 43 ASSERT_TRUE(GetOutputBuffer().empty()); 44 } 45 } 46 47 TEST_P(BacktraceGuardedPoolAllocator, MultipleInvalidFreeOnlyOneOutput) { 48 SCOPED_TRACE(""); 49 char *Ptr = static_cast<char *>(AllocateMemory(GPA)); 50 // First time should generate a crash report. 51 DeallocateMemory(GPA, Ptr + 1); 52 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 53 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Invalid (Wild) Free")); 54 55 // Ensure the crash is only reported once. 56 GetOutputBuffer().clear(); 57 for (size_t i = 0; i < 100; ++i) { 58 DeallocateMemory(GPA, Ptr + 1); 59 ASSERT_TRUE(GetOutputBuffer().empty()); 60 } 61 } 62 63 TEST_P(BacktraceGuardedPoolAllocator, MultipleUseAfterFreeOnlyOneOutput) { 64 SCOPED_TRACE(""); 65 void *Ptr = AllocateMemory(GPA); 66 DeallocateMemory(GPA, Ptr); 67 // First time should generate a crash report. 68 TouchMemory(Ptr); 69 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Use After Free")); 70 71 // Ensure the crash is only reported once. 72 GetOutputBuffer().clear(); 73 for (size_t i = 0; i < 100; ++i) { 74 TouchMemory(Ptr); 75 ASSERT_TRUE(GetOutputBuffer().empty()); 76 } 77 } 78 79 TEST_P(BacktraceGuardedPoolAllocator, MultipleBufferOverflowOnlyOneOutput) { 80 SCOPED_TRACE(""); 81 char *Ptr = static_cast<char *>(AllocateMemory(GPA)); 82 // First time should generate a crash report. 83 TouchMemory(Ptr - 16); 84 TouchMemory(Ptr + 16); 85 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 86 if (GetOutputBuffer().find("Buffer Overflow") == std::string::npos && 87 GetOutputBuffer().find("Buffer Underflow") == std::string::npos) 88 FAIL() << "Failed to detect buffer underflow/overflow:\n" 89 << GetOutputBuffer(); 90 91 // Ensure the crash is only reported once. 92 GetOutputBuffer().clear(); 93 for (size_t i = 0; i < 100; ++i) { 94 TouchMemory(Ptr - 16); 95 TouchMemory(Ptr + 16); 96 ASSERT_TRUE(GetOutputBuffer().empty()) << GetOutputBuffer(); 97 } 98 } 99 100 TEST_P(BacktraceGuardedPoolAllocator, OneDoubleFreeOneUseAfterFree) { 101 SCOPED_TRACE(""); 102 void *Ptr = AllocateMemory(GPA); 103 DeallocateMemory(GPA, Ptr); 104 // First time should generate a crash report. 105 DeallocateMemory(GPA, Ptr); 106 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 107 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); 108 109 // Ensure the crash is only reported once. 110 GetOutputBuffer().clear(); 111 for (size_t i = 0; i < 100; ++i) { 112 DeallocateMemory(GPA, Ptr); 113 ASSERT_TRUE(GetOutputBuffer().empty()); 114 } 115 } 116 117 // We use double-free to detect that each slot can generate as single error. 118 // Use-after-free would also be acceptable, but buffer-overflow wouldn't be, as 119 // the random left/right alignment means that one right-overflow can disable 120 // page protections, and a subsequent left-overflow of a slot that's on the 121 // right hand side may not trap. 122 TEST_P(BacktraceGuardedPoolAllocator, OneErrorReportPerSlot) { 123 SCOPED_TRACE(""); 124 std::vector<void *> Ptrs; 125 for (size_t i = 0; i < GPA.getAllocatorState()->MaxSimultaneousAllocations; 126 ++i) { 127 void *Ptr = AllocateMemory(GPA); 128 ASSERT_NE(Ptr, nullptr); 129 Ptrs.push_back(Ptr); 130 DeallocateMemory(GPA, Ptr); 131 DeallocateMemory(GPA, Ptr); 132 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 133 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free")); 134 // Ensure the crash from this slot is only reported once. 135 GetOutputBuffer().clear(); 136 DeallocateMemory(GPA, Ptr); 137 ASSERT_TRUE(GetOutputBuffer().empty()); 138 // Reset the buffer, as we're gonna move to the next allocation. 139 GetOutputBuffer().clear(); 140 } 141 142 // All slots should have been used. No further errors should occur. 143 for (size_t i = 0; i < 100; ++i) 144 ASSERT_EQ(AllocateMemory(GPA), nullptr); 145 for (void *Ptr : Ptrs) { 146 DeallocateMemory(GPA, Ptr); 147 TouchMemory(Ptr); 148 } 149 ASSERT_TRUE(GetOutputBuffer().empty()); 150 } 151 152 void singleAllocThrashTask(gwp_asan::GuardedPoolAllocator *GPA, 153 std::atomic<bool> *StartingGun, 154 unsigned NumIterations, unsigned Job, char *Ptr) { 155 while (!*StartingGun) { 156 // Wait for starting gun. 157 } 158 159 for (unsigned i = 0; i < NumIterations; ++i) { 160 switch (Job) { 161 case 0: 162 DeallocateMemory(*GPA, Ptr); 163 break; 164 case 1: 165 DeallocateMemory(*GPA, Ptr + 1); 166 break; 167 case 2: 168 TouchMemory(Ptr); 169 break; 170 case 3: 171 TouchMemory(Ptr - 16); 172 TouchMemory(Ptr + 16); 173 break; 174 default: 175 __builtin_trap(); 176 } 177 } 178 } 179 180 void runInterThreadThrashingSingleAlloc(unsigned NumIterations, 181 gwp_asan::GuardedPoolAllocator *GPA) { 182 std::atomic<bool> StartingGun{false}; 183 std::vector<std::thread> Threads; 184 constexpr unsigned kNumThreads = 4; 185 if (std::thread::hardware_concurrency() < kNumThreads) { 186 GTEST_SKIP() << "Not enough threads to run this test"; 187 } 188 189 char *Ptr = static_cast<char *>(AllocateMemory(*GPA)); 190 191 for (unsigned i = 0; i < kNumThreads; ++i) { 192 Threads.emplace_back(singleAllocThrashTask, GPA, &StartingGun, 193 NumIterations, i, Ptr); 194 } 195 196 StartingGun = true; 197 198 for (auto &T : Threads) 199 T.join(); 200 } 201 202 TEST_P(BacktraceGuardedPoolAllocator, InterThreadThrashingSingleAlloc) { 203 SCOPED_TRACE(""); 204 constexpr unsigned kNumIterations = 100000; 205 runInterThreadThrashingSingleAlloc(kNumIterations, &GPA); 206 CheckOnlyOneGwpAsanCrash(GetOutputBuffer()); 207 } 208 209 INSTANTIATE_TEST_SUITE_P(RecoverableTests, BacktraceGuardedPoolAllocator, 210 /* Recoverable */ testing::Values(true)); 211