1 //===-- combined_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 "tests/scudo_unit_test.h"
10 
11 #include "allocator_config.h"
12 #include "combined.h"
13 
14 #include <condition_variable>
15 #include <mutex>
16 #include <thread>
17 #include <vector>
18 
19 static std::mutex Mutex;
20 static std::condition_variable Cv;
21 static bool Ready = false;
22 
23 static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc;
24 
25 static void disableDebuggerdMaybe() {
26 #if SCUDO_ANDROID
27   // Disable the debuggerd signal handler on Android, without this we can end
28   // up spending a significant amount of time creating tombstones.
29   signal(SIGSEGV, SIG_DFL);
30 #endif
31 }
32 
33 template <class AllocatorT>
34 bool isTaggedAllocation(AllocatorT *Allocator, scudo::uptr Size,
35                         scudo::uptr Alignment) {
36   if (!Allocator->useMemoryTagging() ||
37       !scudo::systemDetectsMemoryTagFaultsTestOnly())
38     return false;
39 
40   const scudo::uptr MinAlignment = 1UL << SCUDO_MIN_ALIGNMENT_LOG;
41   if (Alignment < MinAlignment)
42     Alignment = MinAlignment;
43   const scudo::uptr NeededSize =
44       scudo::roundUpTo(Size, MinAlignment) +
45       ((Alignment > MinAlignment) ? Alignment : scudo::Chunk::getHeaderSize());
46   return AllocatorT::PrimaryT::canAllocate(NeededSize);
47 }
48 
49 template <class AllocatorT>
50 void checkMemoryTaggingMaybe(AllocatorT *Allocator, void *P, scudo::uptr Size,
51                              scudo::uptr Alignment) {
52   if (!isTaggedAllocation(Allocator, Size, Alignment))
53     return;
54 
55   Size = scudo::roundUpTo(Size, scudo::archMemoryTagGranuleSize());
56   EXPECT_DEATH(
57       {
58         disableDebuggerdMaybe();
59         reinterpret_cast<char *>(P)[-1] = 0xaa;
60       },
61       "");
62   EXPECT_DEATH(
63       {
64         disableDebuggerdMaybe();
65         reinterpret_cast<char *>(P)[Size] = 0xaa;
66       },
67       "");
68 }
69 
70 template <class Config> static void testAllocator() {
71   using AllocatorT = scudo::Allocator<Config>;
72   auto Deleter = [](AllocatorT *A) {
73     A->unmapTestOnly();
74     delete A;
75   };
76   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
77                                                            Deleter);
78   Allocator->reset();
79 
80   EXPECT_FALSE(Allocator->isOwned(&Mutex));
81   EXPECT_FALSE(Allocator->isOwned(&Allocator));
82   scudo::u64 StackVariable = 0x42424242U;
83   EXPECT_FALSE(Allocator->isOwned(&StackVariable));
84   EXPECT_EQ(StackVariable, 0x42424242U);
85 
86   constexpr scudo::uptr MinAlignLog = FIRST_32_SECOND_64(3U, 4U);
87 
88   // This allocates and deallocates a bunch of chunks, with a wide range of
89   // sizes and alignments, with a focus on sizes that could trigger weird
90   // behaviors (plus or minus a small delta of a power of two for example).
91   for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {
92     for (scudo::uptr AlignLog = MinAlignLog; AlignLog <= 16U; AlignLog++) {
93       const scudo::uptr Align = 1U << AlignLog;
94       for (scudo::sptr Delta = -32; Delta <= 32; Delta++) {
95         if (static_cast<scudo::sptr>(1U << SizeLog) + Delta <= 0)
96           continue;
97         const scudo::uptr Size = (1U << SizeLog) + Delta;
98         void *P = Allocator->allocate(Size, Origin, Align);
99         EXPECT_NE(P, nullptr);
100         EXPECT_TRUE(Allocator->isOwned(P));
101         EXPECT_TRUE(scudo::isAligned(reinterpret_cast<scudo::uptr>(P), Align));
102         EXPECT_LE(Size, Allocator->getUsableSize(P));
103         memset(P, 0xaa, Size);
104         checkMemoryTaggingMaybe(Allocator.get(), P, Size, Align);
105         Allocator->deallocate(P, Origin, Size);
106       }
107     }
108   }
109   Allocator->releaseToOS();
110 
111   // Ensure that specifying ZeroContents returns a zero'd out block.
112   for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {
113     for (scudo::uptr Delta = 0U; Delta <= 4U; Delta++) {
114       const scudo::uptr Size = (1U << SizeLog) + Delta * 128U;
115       void *P = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
116       EXPECT_NE(P, nullptr);
117       for (scudo::uptr I = 0; I < Size; I++)
118         EXPECT_EQ((reinterpret_cast<char *>(P))[I], 0);
119       memset(P, 0xaa, Size);
120       Allocator->deallocate(P, Origin, Size);
121     }
122   }
123   Allocator->releaseToOS();
124 
125   // Verify that a chunk will end up being reused, at some point.
126   const scudo::uptr NeedleSize = 1024U;
127   void *NeedleP = Allocator->allocate(NeedleSize, Origin);
128   Allocator->deallocate(NeedleP, Origin);
129   bool Found = false;
130   for (scudo::uptr I = 0; I < 1024U && !Found; I++) {
131     void *P = Allocator->allocate(NeedleSize, Origin);
132     if (Allocator->untagPointerMaybe(P) ==
133         Allocator->untagPointerMaybe(NeedleP))
134       Found = true;
135     Allocator->deallocate(P, Origin);
136   }
137   EXPECT_TRUE(Found);
138 
139   constexpr scudo::uptr MaxSize = Config::Primary::SizeClassMap::MaxSize;
140 
141   // Reallocate a large chunk all the way down to a byte, verifying that we
142   // preserve the data in the process.
143   scudo::uptr Size = MaxSize * 2;
144   const scudo::uptr DataSize = 2048U;
145   void *P = Allocator->allocate(Size, Origin);
146   const char Marker = 0xab;
147   memset(P, Marker, scudo::Min(Size, DataSize));
148   while (Size > 1U) {
149     Size /= 2U;
150     void *NewP = Allocator->reallocate(P, Size);
151     EXPECT_NE(NewP, nullptr);
152     for (scudo::uptr J = 0; J < scudo::Min(Size, DataSize); J++)
153       EXPECT_EQ((reinterpret_cast<char *>(NewP))[J], Marker);
154     P = NewP;
155   }
156   Allocator->deallocate(P, Origin);
157 
158   // Check that reallocating a chunk to a slightly smaller or larger size
159   // returns the same chunk. This requires that all the sizes we iterate on use
160   // the same block size, but that should be the case for MaxSize - 64 with our
161   // default class size maps.
162   constexpr scudo::uptr ReallocSize = MaxSize - 64;
163   P = Allocator->allocate(ReallocSize, Origin);
164   memset(P, Marker, ReallocSize);
165   for (scudo::sptr Delta = -32; Delta < 32; Delta += 8) {
166     const scudo::uptr NewSize = ReallocSize + Delta;
167     void *NewP = Allocator->reallocate(P, NewSize);
168     EXPECT_EQ(NewP, P);
169     for (scudo::uptr I = 0; I < ReallocSize - 32; I++)
170       EXPECT_EQ((reinterpret_cast<char *>(NewP))[I], Marker);
171     checkMemoryTaggingMaybe(Allocator.get(), NewP, NewSize, 0);
172   }
173   Allocator->deallocate(P, Origin);
174 
175   // Allocates a bunch of chunks, then iterate over all the chunks, ensuring
176   // they are the ones we allocated. This requires the allocator to not have any
177   // other allocated chunk at this point (eg: won't work with the Quarantine).
178   if (!UseQuarantine) {
179     std::vector<void *> V;
180     for (scudo::uptr I = 0; I < 64U; I++)
181       V.push_back(Allocator->allocate(rand() % (MaxSize / 2U), Origin));
182     Allocator->disable();
183     Allocator->iterateOverChunks(
184         0U, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),
185         [](uintptr_t Base, size_t Size, void *Arg) {
186           std::vector<void *> *V = reinterpret_cast<std::vector<void *> *>(Arg);
187           void *P = reinterpret_cast<void *>(Base);
188           EXPECT_NE(std::find(V->begin(), V->end(), P), V->end());
189         },
190         reinterpret_cast<void *>(&V));
191     Allocator->enable();
192     while (!V.empty()) {
193       Allocator->deallocate(V.back(), Origin);
194       V.pop_back();
195     }
196   }
197 
198   Allocator->releaseToOS();
199 
200   if (Allocator->useMemoryTagging() &&
201       scudo::systemDetectsMemoryTagFaultsTestOnly()) {
202     // Check that use-after-free is detected.
203     for (scudo::uptr SizeLog = 0U; SizeLog <= 20U; SizeLog++) {
204       const scudo::uptr Size = 1U << SizeLog;
205       if (!isTaggedAllocation(Allocator.get(), Size, 1))
206         continue;
207       // UAF detection is probabilistic, so we repeat the test up to 256 times
208       // if necessary. With 15 possible tags this means a 1 in 15^256 chance of
209       // a false positive.
210       EXPECT_DEATH(
211           {
212             disableDebuggerdMaybe();
213             for (unsigned I = 0; I != 256; ++I) {
214               void *P = Allocator->allocate(Size, Origin);
215               Allocator->deallocate(P, Origin);
216               reinterpret_cast<char *>(P)[0] = 0xaa;
217             }
218           },
219           "");
220       EXPECT_DEATH(
221           {
222             disableDebuggerdMaybe();
223             for (unsigned I = 0; I != 256; ++I) {
224               void *P = Allocator->allocate(Size, Origin);
225               Allocator->deallocate(P, Origin);
226               reinterpret_cast<char *>(P)[Size - 1] = 0xaa;
227             }
228           },
229           "");
230     }
231 
232     // Check that disabling memory tagging works correctly.
233     void *P = Allocator->allocate(2048, Origin);
234     EXPECT_DEATH(reinterpret_cast<char *>(P)[2048] = 0xaa, "");
235     scudo::disableMemoryTagChecksTestOnly();
236     Allocator->disableMemoryTagging();
237     reinterpret_cast<char *>(P)[2048] = 0xaa;
238     Allocator->deallocate(P, Origin);
239 
240     P = Allocator->allocate(2048, Origin);
241     EXPECT_EQ(Allocator->untagPointerMaybe(P), P);
242     reinterpret_cast<char *>(P)[2048] = 0xaa;
243     Allocator->deallocate(P, Origin);
244 
245     Allocator->releaseToOS();
246 
247     // Disabling memory tag checks may interfere with subsequent tests.
248     // Re-enable them now.
249     scudo::enableMemoryTagChecksTestOnly();
250   }
251 
252   scudo::uptr BufferSize = 8192;
253   std::vector<char> Buffer(BufferSize);
254   scudo::uptr ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
255   while (ActualSize > BufferSize) {
256     BufferSize = ActualSize + 1024;
257     Buffer.resize(BufferSize);
258     ActualSize = Allocator->getStats(Buffer.data(), BufferSize);
259   }
260   std::string Stats(Buffer.begin(), Buffer.end());
261   // Basic checks on the contents of the statistics output, which also allows us
262   // to verify that we got it all.
263   EXPECT_NE(Stats.find("Stats: SizeClassAllocator"), std::string::npos);
264   EXPECT_NE(Stats.find("Stats: MapAllocator"), std::string::npos);
265   EXPECT_NE(Stats.find("Stats: Quarantine"), std::string::npos);
266 }
267 
268 // Test that multiple instantiations of the allocator have not messed up the
269 // process's signal handlers (GWP-ASan used to do this).
270 void testSEGV() {
271   const scudo::uptr Size = 4 * scudo::getPageSizeCached();
272   scudo::MapPlatformData Data = {};
273   void *P = scudo::map(nullptr, Size, "testSEGV", MAP_NOACCESS, &Data);
274   EXPECT_NE(P, nullptr);
275   EXPECT_DEATH(memset(P, 0xaa, Size), "");
276   scudo::unmap(P, Size, UNMAP_ALL, &Data);
277 }
278 
279 TEST(ScudoCombinedTest, BasicCombined) {
280   UseQuarantine = false;
281   testAllocator<scudo::AndroidSvelteConfig>();
282 #if SCUDO_FUCHSIA
283   testAllocator<scudo::FuchsiaConfig>();
284 #else
285   testAllocator<scudo::DefaultConfig>();
286   UseQuarantine = true;
287   testAllocator<scudo::AndroidConfig>();
288   testSEGV();
289 #endif
290 }
291 
292 template <typename AllocatorT> static void stressAllocator(AllocatorT *A) {
293   {
294     std::unique_lock<std::mutex> Lock(Mutex);
295     while (!Ready)
296       Cv.wait(Lock);
297   }
298   std::vector<std::pair<void *, scudo::uptr>> V;
299   for (scudo::uptr I = 0; I < 256U; I++) {
300     const scudo::uptr Size = std::rand() % 4096U;
301     void *P = A->allocate(Size, Origin);
302     // A region could have ran out of memory, resulting in a null P.
303     if (P)
304       V.push_back(std::make_pair(P, Size));
305   }
306   while (!V.empty()) {
307     auto Pair = V.back();
308     A->deallocate(Pair.first, Origin, Pair.second);
309     V.pop_back();
310   }
311 }
312 
313 template <class Config> static void testAllocatorThreaded() {
314   using AllocatorT = scudo::Allocator<Config>;
315   auto Deleter = [](AllocatorT *A) {
316     A->unmapTestOnly();
317     delete A;
318   };
319   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
320                                                            Deleter);
321   Allocator->reset();
322   std::thread Threads[32];
323   for (scudo::uptr I = 0; I < ARRAY_SIZE(Threads); I++)
324     Threads[I] = std::thread(stressAllocator<AllocatorT>, Allocator.get());
325   {
326     std::unique_lock<std::mutex> Lock(Mutex);
327     Ready = true;
328     Cv.notify_all();
329   }
330   for (auto &T : Threads)
331     T.join();
332   Allocator->releaseToOS();
333 }
334 
335 TEST(ScudoCombinedTest, ThreadedCombined) {
336   UseQuarantine = false;
337   testAllocatorThreaded<scudo::AndroidSvelteConfig>();
338 #if SCUDO_FUCHSIA
339   testAllocatorThreaded<scudo::FuchsiaConfig>();
340 #else
341   testAllocatorThreaded<scudo::DefaultConfig>();
342   UseQuarantine = true;
343   testAllocatorThreaded<scudo::AndroidConfig>();
344 #endif
345 }
346 
347 
348 struct DeathSizeClassConfig {
349   static const scudo::uptr NumBits = 1;
350   static const scudo::uptr MinSizeLog = 10;
351   static const scudo::uptr MidSizeLog = 10;
352   static const scudo::uptr MaxSizeLog = 10;
353   static const scudo::u32 MaxNumCachedHint = 1;
354   static const scudo::uptr MaxBytesCachedLog = 10;
355 };
356 
357 struct DeathConfig {
358   // Tiny allocator, its Primary only serves chunks of 1024 bytes.
359   using DeathSizeClassMap = scudo::FixedSizeClassMap<DeathSizeClassConfig>;
360   typedef scudo::SizeClassAllocator64<DeathSizeClassMap, 20U> Primary;
361   typedef scudo::MapAllocator<scudo::MapAllocatorNoCache> Secondary;
362   template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U>;
363 };
364 
365 TEST(ScudoCombinedTest, DeathCombined) {
366   using AllocatorT = scudo::Allocator<DeathConfig>;
367   auto Deleter = [](AllocatorT *A) {
368     A->unmapTestOnly();
369     delete A;
370   };
371   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
372                                                            Deleter);
373   Allocator->reset();
374 
375   const scudo::uptr Size = 1000U;
376   void *P = Allocator->allocate(Size, Origin);
377   EXPECT_NE(P, nullptr);
378 
379   // Invalid sized deallocation.
380   EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");
381 
382   // Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.
383   UNUSED void *MisalignedP =
384       reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);
385   EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");
386   EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");
387 
388   // Header corruption.
389   scudo::u64 *H =
390       reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P));
391   *H ^= 0x42U;
392   EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
393   *H ^= 0x420042U;
394   EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
395   *H ^= 0x420000U;
396 
397   // Invalid chunk state.
398   Allocator->deallocate(P, Origin, Size);
399   EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
400   EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), "");
401   EXPECT_DEATH(Allocator->getUsableSize(P), "");
402 }
403 
404 // Ensure that releaseToOS can be called prior to any other allocator
405 // operation without issue.
406 TEST(ScudoCombinedTest, ReleaseToOS) {
407   using AllocatorT = scudo::Allocator<DeathConfig>;
408   auto Deleter = [](AllocatorT *A) {
409     A->unmapTestOnly();
410     delete A;
411   };
412   std::unique_ptr<AllocatorT, decltype(Deleter)> Allocator(new AllocatorT,
413                                                            Deleter);
414   Allocator->reset();
415 
416   Allocator->releaseToOS();
417 }
418