| 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 | |