xref: /aosp_15_r20/external/gwp_asan/gwp_asan/tests/recoverable.cpp (revision b302aa5039729da396909ef03e815160dab4448c)
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 
TEST_P(BacktraceGuardedPoolAllocator,MultipleDoubleFreeOnlyOneOutput)20 TEST_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 
TEST_P(BacktraceGuardedPoolAllocator,MultipleInvalidFreeOnlyOneOutput)37 TEST_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 
TEST_P(BacktraceGuardedPoolAllocator,MultipleUseAfterFreeOnlyOneOutput)53 TEST_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 
TEST_P(BacktraceGuardedPoolAllocator,MultipleBufferOverflowOnlyOneOutput)69 TEST_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 
TEST_P(BacktraceGuardedPoolAllocator,OneDoubleFreeOneUseAfterFree)90 TEST_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.
TEST_P(BacktraceGuardedPoolAllocator,OneErrorReportPerSlot)112 TEST_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 
singleAllocThrashTask(gwp_asan::GuardedPoolAllocator * GPA,std::atomic<bool> * StartingGun,unsigned NumIterations,unsigned Job,char * Ptr)142 void 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 
runInterThreadThrashingSingleAlloc(unsigned NumIterations,gwp_asan::GuardedPoolAllocator * GPA)170 void 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 
TEST_P(BacktraceGuardedPoolAllocator,InterThreadThrashingSingleAlloc)189 TEST_P(BacktraceGuardedPoolAllocator, InterThreadThrashingSingleAlloc) {
190   SCOPED_TRACE("");
191   constexpr unsigned kNumIterations = 100000;
192   runInterThreadThrashingSingleAlloc(kNumIterations, &GPA);
193   CheckOnlyOneGwpAsanCrash(GetOutputBuffer());
194 }
195