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
CheckOnlyOneGwpAsanCrash(const std::string & OutputBuffer)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
TEST_P(BacktraceGuardedPoolAllocator,MultipleDoubleFreeOnlyOneOutput)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
TEST_P(BacktraceGuardedPoolAllocator,MultipleInvalidFreeOnlyOneOutput)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
TEST_P(BacktraceGuardedPoolAllocator,MultipleUseAfterFreeOnlyOneOutput)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
TEST_P(BacktraceGuardedPoolAllocator,MultipleBufferOverflowOnlyOneOutput)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
TEST_P(BacktraceGuardedPoolAllocator,OneDoubleFreeOneUseAfterFree)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.
TEST_P(BacktraceGuardedPoolAllocator,OneErrorReportPerSlot)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
singleAllocThrashTask(gwp_asan::GuardedPoolAllocator * GPA,std::atomic<bool> * StartingGun,unsigned NumIterations,unsigned Job,char * Ptr)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
runInterThreadThrashingSingleAlloc(unsigned NumIterations,gwp_asan::GuardedPoolAllocator * GPA)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
TEST_P(BacktraceGuardedPoolAllocator,InterThreadThrashingSingleAlloc)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