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