1 | //===-- wrappers_cpp_test.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 "memtag.h" |
10 | #include "tests/scudo_unit_test.h" |
11 | |
12 | #include <atomic> |
13 | #include <condition_variable> |
14 | #include <fstream> |
15 | #include <memory> |
16 | #include <mutex> |
17 | #include <thread> |
18 | #include <vector> |
19 | |
20 | // Android does not support checking for new/delete mismatches. |
21 | #if SCUDO_ANDROID |
22 | #define SKIP_MISMATCH_TESTS 1 |
23 | #else |
24 | #define SKIP_MISMATCH_TESTS 0 |
25 | #endif |
26 | |
27 | void operator delete(void *, size_t) noexcept; |
28 | void operator delete[](void *, size_t) noexcept; |
29 | |
30 | extern "C" { |
31 | #ifndef SCUDO_ENABLE_HOOKS_TESTS |
32 | #define SCUDO_ENABLE_HOOKS_TESTS 0 |
33 | #endif |
34 | |
35 | #if (SCUDO_ENABLE_HOOKS_TESTS == 1) && (SCUDO_ENABLE_HOOKS == 0) |
36 | #error "Hooks tests should have hooks enabled as well!" |
37 | #endif |
38 | |
39 | struct AllocContext { |
40 | void *Ptr; |
41 | size_t Size; |
42 | }; |
43 | struct DeallocContext { |
44 | void *Ptr; |
45 | }; |
46 | static AllocContext AC; |
47 | static DeallocContext DC; |
48 | |
49 | #if (SCUDO_ENABLE_HOOKS_TESTS == 1) |
50 | __attribute__((visibility("default" ))) void __scudo_allocate_hook(void *Ptr, |
51 | size_t Size) { |
52 | AC.Ptr = Ptr; |
53 | AC.Size = Size; |
54 | } |
55 | __attribute__((visibility("default" ))) void __scudo_deallocate_hook(void *Ptr) { |
56 | DC.Ptr = Ptr; |
57 | } |
58 | #endif // (SCUDO_ENABLE_HOOKS_TESTS == 1) |
59 | } |
60 | |
61 | class ScudoWrappersCppTest : public Test { |
62 | protected: |
63 | void SetUp() override { |
64 | if (SCUDO_ENABLE_HOOKS && !SCUDO_ENABLE_HOOKS_TESTS) |
65 | printf("Hooks are enabled but hooks tests are disabled.\n" ); |
66 | } |
67 | |
68 | void verifyAllocHookPtr(UNUSED void *Ptr) { |
69 | if (SCUDO_ENABLE_HOOKS_TESTS) |
70 | EXPECT_EQ(Ptr, AC.Ptr); |
71 | } |
72 | void verifyAllocHookSize(UNUSED size_t Size) { |
73 | if (SCUDO_ENABLE_HOOKS_TESTS) |
74 | EXPECT_EQ(Size, AC.Size); |
75 | } |
76 | void verifyDeallocHookPtr(UNUSED void *Ptr) { |
77 | if (SCUDO_ENABLE_HOOKS_TESTS) |
78 | EXPECT_EQ(Ptr, DC.Ptr); |
79 | } |
80 | |
81 | template <typename T> void testCxxNew() { |
82 | T *P = new T; |
83 | EXPECT_NE(P, nullptr); |
84 | verifyAllocHookPtr(Ptr: P); |
85 | verifyAllocHookSize(sizeof(T)); |
86 | memset(P, 0x42, sizeof(T)); |
87 | EXPECT_DEATH(delete[] P, "" ); |
88 | delete P; |
89 | verifyDeallocHookPtr(Ptr: P); |
90 | EXPECT_DEATH(delete P, "" ); |
91 | |
92 | P = new T; |
93 | EXPECT_NE(P, nullptr); |
94 | memset(P, 0x42, sizeof(T)); |
95 | operator delete(P, sizeof(T)); |
96 | verifyDeallocHookPtr(Ptr: P); |
97 | |
98 | P = new (std::nothrow) T; |
99 | verifyAllocHookPtr(Ptr: P); |
100 | verifyAllocHookSize(sizeof(T)); |
101 | EXPECT_NE(P, nullptr); |
102 | memset(P, 0x42, sizeof(T)); |
103 | delete P; |
104 | verifyDeallocHookPtr(Ptr: P); |
105 | |
106 | const size_t N = 16U; |
107 | T *A = new T[N]; |
108 | EXPECT_NE(A, nullptr); |
109 | verifyAllocHookPtr(Ptr: A); |
110 | verifyAllocHookSize(sizeof(T) * N); |
111 | memset(A, 0x42, sizeof(T) * N); |
112 | EXPECT_DEATH(delete A, "" ); |
113 | delete[] A; |
114 | verifyDeallocHookPtr(Ptr: A); |
115 | EXPECT_DEATH(delete[] A, "" ); |
116 | |
117 | A = new T[N]; |
118 | EXPECT_NE(A, nullptr); |
119 | memset(A, 0x42, sizeof(T) * N); |
120 | operator delete[](A, sizeof(T) * N); |
121 | verifyDeallocHookPtr(Ptr: A); |
122 | |
123 | A = new (std::nothrow) T[N]; |
124 | verifyAllocHookPtr(Ptr: A); |
125 | verifyAllocHookSize(sizeof(T) * N); |
126 | EXPECT_NE(A, nullptr); |
127 | memset(A, 0x42, sizeof(T) * N); |
128 | delete[] A; |
129 | verifyDeallocHookPtr(Ptr: A); |
130 | } |
131 | }; |
132 | using ScudoWrappersCppDeathTest = ScudoWrappersCppTest; |
133 | |
134 | class Pixel { |
135 | public: |
136 | enum class Color { Red, Green, Blue }; |
137 | int X = 0; |
138 | int Y = 0; |
139 | Color C = Color::Red; |
140 | }; |
141 | |
142 | // Note that every Cxx allocation function in the test binary will be fulfilled |
143 | // by Scudo. See the comment in the C counterpart of this file. |
144 | |
145 | TEST_F(ScudoWrappersCppDeathTest, New) { |
146 | if (getenv("SKIP_TYPE_MISMATCH" ) || SKIP_MISMATCH_TESTS) { |
147 | printf("Skipped type mismatch tests.\n" ); |
148 | return; |
149 | } |
150 | testCxxNew<bool>(); |
151 | testCxxNew<uint8_t>(); |
152 | testCxxNew<uint16_t>(); |
153 | testCxxNew<uint32_t>(); |
154 | testCxxNew<uint64_t>(); |
155 | testCxxNew<float>(); |
156 | testCxxNew<double>(); |
157 | testCxxNew<long double>(); |
158 | testCxxNew<Pixel>(); |
159 | } |
160 | |
161 | static std::mutex Mutex; |
162 | static std::condition_variable Cv; |
163 | static bool Ready; |
164 | |
165 | static void stressNew() { |
166 | std::vector<uintptr_t *> V; |
167 | { |
168 | std::unique_lock<std::mutex> Lock(Mutex); |
169 | while (!Ready) |
170 | Cv.wait(Lock); |
171 | } |
172 | for (size_t I = 0; I < 256U; I++) { |
173 | const size_t N = static_cast<size_t>(std::rand()) % 128U; |
174 | uintptr_t *P = new uintptr_t[N]; |
175 | if (P) { |
176 | memset(P, 0x42, sizeof(uintptr_t) * N); |
177 | V.push_back(P); |
178 | } |
179 | } |
180 | while (!V.empty()) { |
181 | delete[] V.back(); |
182 | V.pop_back(); |
183 | } |
184 | } |
185 | |
186 | TEST_F(ScudoWrappersCppTest, ThreadedNew) { |
187 | // TODO: Investigate why libc sometimes crashes with tag missmatch in |
188 | // __pthread_clockjoin_ex. |
189 | std::unique_ptr<scudo::ScopedDisableMemoryTagChecks> NoTags; |
190 | if (!SCUDO_ANDROID && scudo::archSupportsMemoryTagging() && |
191 | scudo::systemSupportsMemoryTagging()) |
192 | NoTags = std::make_unique<scudo::ScopedDisableMemoryTagChecks>(); |
193 | |
194 | Ready = false; |
195 | std::thread Threads[32]; |
196 | for (size_t I = 0U; I < sizeof(Threads) / sizeof(Threads[0]); I++) |
197 | Threads[I] = std::thread(stressNew); |
198 | { |
199 | std::unique_lock<std::mutex> Lock(Mutex); |
200 | Ready = true; |
201 | Cv.notify_all(); |
202 | } |
203 | for (auto &T : Threads) |
204 | T.join(); |
205 | } |
206 | |
207 | #if !SCUDO_FUCHSIA |
208 | TEST_F(ScudoWrappersCppTest, AllocAfterFork) { |
209 | // This test can fail flakily when ran as a part of large number of |
210 | // other tests if the maxmimum number of mappings allowed is low. |
211 | // We tried to reduce the number of iterations of the loops with |
212 | // moderate success, so we will now skip this test under those |
213 | // circumstances. |
214 | if (SCUDO_LINUX) { |
215 | long MaxMapCount = 0; |
216 | // If the file can't be accessed, we proceed with the test. |
217 | std::ifstream Stream("/proc/sys/vm/max_map_count" ); |
218 | if (Stream.good()) { |
219 | Stream >> MaxMapCount; |
220 | if (MaxMapCount < 200000) |
221 | return; |
222 | } |
223 | } |
224 | |
225 | std::atomic_bool Stop; |
226 | |
227 | // Create threads that simply allocate and free different sizes. |
228 | std::vector<std::thread *> Threads; |
229 | for (size_t N = 0; N < 5; N++) { |
230 | std::thread *T = new std::thread([&Stop] { |
231 | while (!Stop) { |
232 | for (size_t SizeLog = 3; SizeLog <= 20; SizeLog++) { |
233 | char *P = new char[1UL << SizeLog]; |
234 | EXPECT_NE(P, nullptr); |
235 | // Make sure this value is not optimized away. |
236 | asm volatile("" : : "r,m" (P) : "memory" ); |
237 | delete[] P; |
238 | } |
239 | } |
240 | }); |
241 | Threads.push_back(T); |
242 | } |
243 | |
244 | // Create a thread to fork and allocate. |
245 | for (size_t N = 0; N < 50; N++) { |
246 | pid_t Pid; |
247 | if ((Pid = fork()) == 0) { |
248 | for (size_t SizeLog = 3; SizeLog <= 20; SizeLog++) { |
249 | char *P = new char[1UL << SizeLog]; |
250 | EXPECT_NE(P, nullptr); |
251 | // Make sure this value is not optimized away. |
252 | asm volatile("" : : "r,m" (P) : "memory" ); |
253 | // Make sure we can touch all of the allocation. |
254 | memset(P, 0x32, 1U << SizeLog); |
255 | // EXPECT_LE(1U << SizeLog, malloc_usable_size(ptr)); |
256 | delete[] P; |
257 | } |
258 | _exit(10); |
259 | } |
260 | EXPECT_NE(-1, Pid); |
261 | int Status; |
262 | EXPECT_EQ(Pid, waitpid(Pid, &Status, 0)); |
263 | EXPECT_FALSE(WIFSIGNALED(Status)); |
264 | EXPECT_EQ(10, WEXITSTATUS(Status)); |
265 | } |
266 | |
267 | printf("Waiting for threads to complete\n" ); |
268 | Stop = true; |
269 | for (auto Thread : Threads) |
270 | Thread->join(); |
271 | Threads.clear(); |
272 | } |
273 | #endif |
274 | |