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
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: Ptr - 16);
74 TouchMemory(Ptr: 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: Ptr - 16);
85 TouchMemory(Ptr: 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, as
109// the random left/right alignment means that one right-overflow can disable
110// page protections, and a subsequent left-overflow of a slot that's on the
111// 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&: *GPA, Ptr);
153 break;
154 case 1:
155 DeallocateMemory(GPA&: *GPA, Ptr: Ptr + 1);
156 break;
157 case 2:
158 TouchMemory(Ptr);
159 break;
160 case 3:
161 TouchMemory(Ptr: Ptr - 16);
162 TouchMemory(Ptr: 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&: *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

source code of compiler-rt/lib/gwp_asan/tests/recoverable.cpp