195 lines · cpp
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-exception6//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 20TEST_P(BacktraceGuardedPoolAllocator, MultipleDoubleFreeOnlyOneOutput) {21 SCOPED_TRACE("");22 void *Ptr = AllocateMemory(GPA);23 DeallocateMemory(GPA, Ptr);24 // First time should generate a crash report.25 DeallocateMemory(GPA, Ptr);26 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());27 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free"));28 29 // Ensure the crash is only reported once.30 GetOutputBuffer().clear();31 for (size_t i = 0; i < 100; ++i) {32 DeallocateMemory(GPA, Ptr);33 ASSERT_TRUE(GetOutputBuffer().empty());34 }35}36 37TEST_P(BacktraceGuardedPoolAllocator, MultipleInvalidFreeOnlyOneOutput) {38 SCOPED_TRACE("");39 char *Ptr = static_cast<char *>(AllocateMemory(GPA));40 // First time should generate a crash report.41 DeallocateMemory(GPA, Ptr + 1);42 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());43 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Invalid (Wild) Free"));44 45 // Ensure the crash is only reported once.46 GetOutputBuffer().clear();47 for (size_t i = 0; i < 100; ++i) {48 DeallocateMemory(GPA, Ptr + 1);49 ASSERT_TRUE(GetOutputBuffer().empty());50 }51}52 53TEST_P(BacktraceGuardedPoolAllocator, MultipleUseAfterFreeOnlyOneOutput) {54 SCOPED_TRACE("");55 void *Ptr = AllocateMemory(GPA);56 DeallocateMemory(GPA, Ptr);57 // First time should generate a crash report.58 TouchMemory(Ptr);59 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Use After Free"));60 61 // Ensure the crash is only reported once.62 GetOutputBuffer().clear();63 for (size_t i = 0; i < 100; ++i) {64 TouchMemory(Ptr);65 ASSERT_TRUE(GetOutputBuffer().empty());66 }67}68 69TEST_P(BacktraceGuardedPoolAllocator, MultipleBufferOverflowOnlyOneOutput) {70 SCOPED_TRACE("");71 char *Ptr = static_cast<char *>(AllocateMemory(GPA));72 // First time should generate a crash report.73 TouchMemory(Ptr - 16);74 TouchMemory(Ptr + 16);75 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());76 if (GetOutputBuffer().find("Buffer Overflow") == std::string::npos &&77 GetOutputBuffer().find("Buffer Underflow") == std::string::npos)78 FAIL() << "Failed to detect buffer underflow/overflow:\n"79 << GetOutputBuffer();80 81 // Ensure the crash is only reported once.82 GetOutputBuffer().clear();83 for (size_t i = 0; i < 100; ++i) {84 TouchMemory(Ptr - 16);85 TouchMemory(Ptr + 16);86 ASSERT_TRUE(GetOutputBuffer().empty()) << GetOutputBuffer();87 }88}89 90TEST_P(BacktraceGuardedPoolAllocator, OneDoubleFreeOneUseAfterFree) {91 SCOPED_TRACE("");92 void *Ptr = AllocateMemory(GPA);93 DeallocateMemory(GPA, Ptr);94 // First time should generate a crash report.95 DeallocateMemory(GPA, Ptr);96 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());97 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free"));98 99 // Ensure the crash is only reported once.100 GetOutputBuffer().clear();101 for (size_t i = 0; i < 100; ++i) {102 DeallocateMemory(GPA, Ptr);103 ASSERT_TRUE(GetOutputBuffer().empty());104 }105}106 107// We use double-free to detect that each slot can generate as single error.108// Use-after-free would also be acceptable, but buffer-overflow wouldn't be, as109// the random left/right alignment means that one right-overflow can disable110// page protections, and a subsequent left-overflow of a slot that's on the111// right hand side may not trap.112TEST_P(BacktraceGuardedPoolAllocator, OneErrorReportPerSlot) {113 SCOPED_TRACE("");114 std::vector<void *> Ptrs;115 for (size_t i = 0; i < GPA.getAllocatorState()->MaxSimultaneousAllocations;116 ++i) {117 void *Ptr = AllocateMemory(GPA);118 ASSERT_NE(Ptr, nullptr);119 Ptrs.push_back(Ptr);120 DeallocateMemory(GPA, Ptr);121 DeallocateMemory(GPA, Ptr);122 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());123 ASSERT_NE(std::string::npos, GetOutputBuffer().find("Double Free"));124 // Ensure the crash from this slot is only reported once.125 GetOutputBuffer().clear();126 DeallocateMemory(GPA, Ptr);127 ASSERT_TRUE(GetOutputBuffer().empty());128 // Reset the buffer, as we're gonna move to the next allocation.129 GetOutputBuffer().clear();130 }131 132 // All slots should have been used. No further errors should occur.133 for (size_t i = 0; i < 100; ++i)134 ASSERT_EQ(AllocateMemory(GPA), nullptr);135 for (void *Ptr : Ptrs) {136 DeallocateMemory(GPA, Ptr);137 TouchMemory(Ptr);138 }139 ASSERT_TRUE(GetOutputBuffer().empty());140}141 142void singleAllocThrashTask(gwp_asan::GuardedPoolAllocator *GPA,143 std::atomic<bool> *StartingGun,144 unsigned NumIterations, unsigned Job, char *Ptr) {145 while (!*StartingGun) {146 // Wait for starting gun.147 }148 149 for (unsigned i = 0; i < NumIterations; ++i) {150 switch (Job) {151 case 0:152 DeallocateMemory(*GPA, Ptr);153 break;154 case 1:155 DeallocateMemory(*GPA, Ptr + 1);156 break;157 case 2:158 TouchMemory(Ptr);159 break;160 case 3:161 TouchMemory(Ptr - 16);162 TouchMemory(Ptr + 16);163 break;164 default:165 __builtin_trap();166 }167 }168}169 170void runInterThreadThrashingSingleAlloc(unsigned NumIterations,171 gwp_asan::GuardedPoolAllocator *GPA) {172 std::atomic<bool> StartingGun{false};173 std::vector<std::thread> Threads;174 constexpr unsigned kNumThreads = 4;175 176 char *Ptr = static_cast<char *>(AllocateMemory(*GPA));177 178 for (unsigned i = 0; i < kNumThreads; ++i) {179 Threads.emplace_back(singleAllocThrashTask, GPA, &StartingGun,180 NumIterations, i, Ptr);181 }182 183 StartingGun = true;184 185 for (auto &T : Threads)186 T.join();187}188 189TEST_P(BacktraceGuardedPoolAllocator, InterThreadThrashingSingleAlloc) {190 SCOPED_TRACE("");191 constexpr unsigned kNumIterations = 100000;192 runInterThreadThrashingSingleAlloc(kNumIterations, &GPA);193 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());194}195