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