xref: /openbsd-src/gnu/llvm/compiler-rt/lib/gwp_asan/tests/recoverable.cpp (revision 810390e339a5425391477d5d41c78d7cab2424ac)
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