1 //===-- recoverable.cpp -----------------------------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
16 #include "gwp_asan/common.h"
17 #include "gwp_asan/crash_handler.h"
18 #include "gwp_asan/tests/harness.h"
20 TEST_P(BacktraceGuardedPoolAllocator
, MultipleDoubleFreeOnlyOneOutput
) {
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"));
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());
37 TEST_P(BacktraceGuardedPoolAllocator
, MultipleInvalidFreeOnlyOneOutput
) {
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"));
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());
53 TEST_P(BacktraceGuardedPoolAllocator
, MultipleUseAfterFreeOnlyOneOutput
) {
55 void *Ptr
= AllocateMemory(GPA
);
56 DeallocateMemory(GPA
, Ptr
);
57 // First time should generate a crash report.
59 ASSERT_NE(std::string::npos
, GetOutputBuffer().find("Use After Free"));
61 // Ensure the crash is only reported once.
62 GetOutputBuffer().clear();
63 for (size_t i
= 0; i
< 100; ++i
) {
65 ASSERT_TRUE(GetOutputBuffer().empty());
69 TEST_P(BacktraceGuardedPoolAllocator
, MultipleBufferOverflowOnlyOneOutput
) {
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"
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();
90 TEST_P(BacktraceGuardedPoolAllocator
, OneDoubleFreeOneUseAfterFree
) {
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"));
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());
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.
112 TEST_P(BacktraceGuardedPoolAllocator
, OneErrorReportPerSlot
) {
114 std::vector
<void *> Ptrs
;
115 for (size_t i
= 0; i
< GPA
.getAllocatorState()->MaxSimultaneousAllocations
;
117 void *Ptr
= AllocateMemory(GPA
);
118 ASSERT_NE(Ptr
, nullptr);
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();
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
);
139 ASSERT_TRUE(GetOutputBuffer().empty());
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.
149 for (unsigned i
= 0; i
< NumIterations
; ++i
) {
152 DeallocateMemory(*GPA
, Ptr
);
155 DeallocateMemory(*GPA
, Ptr
+ 1);
161 TouchMemory(Ptr
- 16);
162 TouchMemory(Ptr
+ 16);
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;
176 char *Ptr
= static_cast<char *>(AllocateMemory(*GPA
));
178 for (unsigned i
= 0; i
< kNumThreads
; ++i
) {
179 Threads
.emplace_back(singleAllocThrashTask
, GPA
, &StartingGun
,
180 NumIterations
, i
, Ptr
);
185 for (auto &T
: Threads
)
189 TEST_P(BacktraceGuardedPoolAllocator
, InterThreadThrashingSingleAlloc
) {
191 constexpr unsigned kNumIterations
= 100000;
192 runInterThreadThrashingSingleAlloc(kNumIterations
, &GPA
);
193 CheckOnlyOneGwpAsanCrash(GetOutputBuffer());