1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <cstdint>
6 #include <string>
7 #include <vector>
8 
9 #include "build/build_config.h"
10 #include "partition_alloc/partition_alloc_config.h"
11 #include "partition_alloc/partition_freelist_entry.h"
12 #include "partition_alloc/partition_page.h"
13 #include "partition_alloc/partition_root.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 // With *SAN, PartitionAlloc is rerouted to malloc().
17 #if !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
18 
19 namespace partition_alloc::internal {
20 namespace {
21 
22 // Death tests misbehave on Android, crbug.com/1240184
23 #if !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) && \
24     PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY)
25 
TEST(HardeningTest,PartialCorruption)26 TEST(HardeningTest, PartialCorruption) {
27   std::string important_data("very important");
28   char* to_corrupt = const_cast<char*>(important_data.c_str());
29 
30   PartitionOptions opts;
31   PartitionRoot root(opts);
32   root.UncapEmptySlotSpanMemoryForTesting();
33 
34   const size_t kAllocSize = 100;
35   void* data = root.Alloc(kAllocSize);
36   void* data2 = root.Alloc(kAllocSize);
37   root.Free(data2);
38   root.Free(data);
39 
40   // root->bucket->active_slot_span_head->freelist_head points to data, next_
41   // points to data2. We can corrupt *data to overwrite the next_ pointer.
42   // Even if it looks reasonable (valid encoded pointer), freelist corruption
43   // detection will make the code crash, because shadow_ doesn't match
44   // encoded_next_.
45   root.get_freelist_dispatcher()->EmplaceAndInitForTest(
46       root.ObjectToSlotStart(data), to_corrupt, false);
47 
48   EXPECT_DEATH(root.Alloc(kAllocSize), "");
49 }
50 
TEST(HardeningTest,OffHeapPointerCrashing)51 TEST(HardeningTest, OffHeapPointerCrashing) {
52   std::string important_data("very important");
53   char* to_corrupt = const_cast<char*>(important_data.c_str());
54 
55   PartitionOptions opts;
56   PartitionRoot root(opts);
57   root.UncapEmptySlotSpanMemoryForTesting();
58 
59   const size_t kAllocSize = 100;
60   void* data = root.Alloc(kAllocSize);
61   void* data2 = root.Alloc(kAllocSize);
62   root.Free(data2);
63   root.Free(data);
64 
65   // See "PartialCorruption" above for details. This time, make shadow_
66   // consistent.
67   root.get_freelist_dispatcher()->EmplaceAndInitForTest(
68       root.ObjectToSlotStart(data), to_corrupt, true);
69 
70   // Crashes, because |to_corrupt| is not on the same superpage as data.
71   EXPECT_DEATH(root.Alloc(kAllocSize), "");
72 }
73 
TEST(HardeningTest,MetadataPointerCrashing)74 TEST(HardeningTest, MetadataPointerCrashing) {
75   PartitionOptions opts;
76   PartitionRoot root(opts);
77   root.UncapEmptySlotSpanMemoryForTesting();
78 
79   const size_t kAllocSize = 100;
80   void* data = root.Alloc(kAllocSize);
81   void* data2 = root.Alloc(kAllocSize);
82   root.Free(data2);
83   root.Free(data);
84 
85   uintptr_t slot_start = root.ObjectToSlotStart(data);
86   auto* metadata = SlotSpanMetadata::FromSlotStart(slot_start);
87 
88   root.get_freelist_dispatcher()->EmplaceAndInitForTest(slot_start, metadata,
89                                                         true);
90 
91   // Crashes, because |metadata| points inside the metadata area.
92   EXPECT_DEATH(root.Alloc(kAllocSize), "");
93 }
94 #endif  // !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) &&
95         // PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY)
96 
97 // Below test also misbehaves on Android; as above, death tests don't
98 // quite work (crbug.com/1240184), and having free slot bitmaps enabled
99 // force the expectations below to crash.
100 #if !BUILDFLAG(IS_ANDROID)
101 
TEST(HardeningTest,SuccessfulCorruption)102 TEST(HardeningTest, SuccessfulCorruption) {
103   PartitionOptions opts;
104   PartitionRoot root(opts);
105   root.UncapEmptySlotSpanMemoryForTesting();
106 
107   uintptr_t* zero_vector = reinterpret_cast<uintptr_t*>(
108       root.Alloc<AllocFlags::kZeroFill>(100 * sizeof(uintptr_t), ""));
109   ASSERT_TRUE(zero_vector);
110   // Pointer to the middle of an existing allocation.
111   uintptr_t* to_corrupt = zero_vector + 20;
112 
113   const size_t kAllocSize = 100;
114   void* data = root.Alloc(kAllocSize);
115   void* data2 = root.Alloc(kAllocSize);
116   root.Free(data2);
117   root.Free(data);
118 
119   root.get_freelist_dispatcher()->EmplaceAndInitForTest(
120       root.ObjectToSlotStart(data), to_corrupt, true);
121 
122 #if BUILDFLAG(USE_FREESLOT_BITMAP)
123   // This part crashes with freeslot bitmap because it detects freelist
124   // corruptions, which is rather desirable behavior.
125   EXPECT_DEATH_IF_SUPPORTED(root.Alloc(kAllocSize), "");
126 #else
127   // Next allocation is what was in
128   // root->bucket->active_slot_span_head->freelist_head, so not the corrupted
129   // pointer.
130   void* new_data = root.Alloc(kAllocSize);
131   ASSERT_EQ(new_data, data);
132 
133   // Not crashing, because a zeroed area is a "valid" freelist entry.
134   void* new_data2 = root.Alloc(kAllocSize);
135   // Now we have a pointer to the middle of an existing allocation.
136   EXPECT_EQ(new_data2, to_corrupt);
137 #endif  // BUILDFLAG(USE_FREESLOT_BITMAP)
138 }
139 #endif  // !BUILDFLAG(IS_ANDROID)
140 
141 #if BUILDFLAG(USE_FREELIST_POOL_OFFSETS)
142 #if !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) && \
143     PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY)
TEST(HardeningTest,ConstructPoolOffsetFromStackPointerCrashing)144 TEST(HardeningTest, ConstructPoolOffsetFromStackPointerCrashing) {
145   int num_to_corrupt = 12345;
146   int* to_corrupt = &num_to_corrupt;
147 
148   PartitionOptions opts;
149   opts.use_pool_offset_freelists = PartitionOptions::kEnabled;
150   PartitionRoot root(opts);
151   root.UncapEmptySlotSpanMemoryForTesting();
152 
153   const size_t kAllocSize = 100;
154   void* data = root.Alloc(kAllocSize);
155 
156   EXPECT_DEATH(root.get_freelist_dispatcher()->EmplaceAndInitForTest(
157                    root.ObjectToSlotStart(data), to_corrupt, true),
158                "");
159 }
160 
TEST(HardeningTest,PoolOffsetMetadataPointerCrashing)161 TEST(HardeningTest, PoolOffsetMetadataPointerCrashing) {
162   PartitionOptions opts;
163   opts.use_pool_offset_freelists = PartitionOptions::kEnabled;
164   PartitionRoot root(opts);
165   root.UncapEmptySlotSpanMemoryForTesting();
166 
167   const size_t kAllocSize = 100;
168   void* data = root.Alloc(kAllocSize);
169   void* data2 = root.Alloc(kAllocSize);
170   root.Free(data2);
171   root.Free(data);
172 
173   uintptr_t slot_start = root.ObjectToSlotStart(data);
174   auto* metadata = SlotSpanMetadata::FromSlotStart(slot_start);
175 
176   root.get_freelist_dispatcher()->EmplaceAndInitForTest(slot_start, metadata,
177                                                         true);
178 
179   // Crashes, because |metadata| points inside the metadata area.
180   EXPECT_DEATH(root.Alloc(kAllocSize), "");
181 }
182 #endif  // !BUILDFLAG(IS_ANDROID) && defined(GTEST_HAS_DEATH_TEST) &&
183         // PA_CONFIG(HAS_FREELIST_SHADOW_ENTRY)
184 
185 #if !BUILDFLAG(IS_ANDROID)
186 
TEST(HardeningTest,PoolOffsetSuccessfulCorruption)187 TEST(HardeningTest, PoolOffsetSuccessfulCorruption) {
188   PartitionOptions opts;
189   opts.use_pool_offset_freelists = PartitionOptions::kEnabled;
190   PartitionRoot root(opts);
191   root.UncapEmptySlotSpanMemoryForTesting();
192 
193   uintptr_t* zero_vector = reinterpret_cast<uintptr_t*>(
194       root.Alloc<AllocFlags::kZeroFill>(100 * sizeof(uintptr_t), ""));
195   ASSERT_TRUE(zero_vector);
196   // Pointer to the middle of an existing allocation.
197   uintptr_t* to_corrupt = zero_vector + 20;
198 
199   const size_t kAllocSize = 100;
200   void* data = root.Alloc(kAllocSize);
201   void* data2 = root.Alloc(kAllocSize);
202   root.Free(data2);
203   root.Free(data);
204 
205   root.get_freelist_dispatcher()->EmplaceAndInitForTest(
206       root.ObjectToSlotStart(data), to_corrupt, true);
207 
208 #if BUILDFLAG(USE_FREESLOT_BITMAP)
209   // This part crashes with freeslot bitmap because it detects freelist
210   // corruptions, which is rather desirable behavior.
211   EXPECT_DEATH_IF_SUPPORTED(root.Alloc(kAllocSize), "");
212 #else
213   // Next allocation is what was in
214   // root->bucket->active_slot_span_head->freelist_head, so not the corrupted
215   // pointer.
216   void* new_data = root.Alloc(kAllocSize);
217   ASSERT_EQ(new_data, data);
218 
219   // Not crashing, because a zeroed area is a "valid" freelist entry.
220   void* new_data2 = root.Alloc(kAllocSize);
221   // Now we have a pointer to the middle of an existing allocation.
222   EXPECT_EQ(new_data2, to_corrupt);
223 #endif  // BUILDFLAG(USE_FREESLOT_BITMAP)
224 }
225 #endif  // !BUILDFLAG(IS_ANDROID)
226 #endif  // USE_FREELIST_POOL_OFFSETS
227 }  // namespace
228 }  // namespace partition_alloc::internal
229 
230 #endif  // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
231