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 opts.aligned_alloc = PartitionOptions::kAllowed;
32 PartitionRoot root(opts);
33 root.UncapEmptySlotSpanMemoryForTesting();
34
35 const size_t kAllocSize = 100;
36 void* data = root.Alloc(kAllocSize);
37 void* data2 = root.Alloc(kAllocSize);
38 root.Free(data2);
39 root.Free(data);
40
41 // root->bucket->active_slot_span_head->freelist_head points to data, next_
42 // points to data2. We can corrupt *data to get overwrite the next_ pointer.
43 // Even if it looks reasonable (valid encoded pointer), freelist corruption
44 // detection will make the code crash, because shadow_ doesn't match
45 // encoded_next_.
46 EncodedNextFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
47 to_corrupt, false);
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 opts.aligned_alloc = PartitionOptions::kAllowed;
57 PartitionRoot root(opts);
58 root.UncapEmptySlotSpanMemoryForTesting();
59
60 const size_t kAllocSize = 100;
61 void* data = root.Alloc(kAllocSize);
62 void* data2 = root.Alloc(kAllocSize);
63 root.Free(data2);
64 root.Free(data);
65
66 // See "PartialCorruption" above for details. This time, make shadow_
67 // consistent.
68 EncodedNextFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
69 to_corrupt, true);
70
71 // Crashes, because |to_corrupt| is not on the same superpage as data.
72 EXPECT_DEATH(root.Alloc(kAllocSize), "");
73 }
74
TEST(HardeningTest,MetadataPointerCrashing)75 TEST(HardeningTest, MetadataPointerCrashing) {
76 PartitionOptions opts;
77 opts.aligned_alloc = PartitionOptions::kAllowed;
78 PartitionRoot root(opts);
79 root.UncapEmptySlotSpanMemoryForTesting();
80
81 const size_t kAllocSize = 100;
82 void* data = root.Alloc(kAllocSize);
83 void* data2 = root.Alloc(kAllocSize);
84 root.Free(data2);
85 root.Free(data);
86
87 uintptr_t slot_start = root.ObjectToSlotStart(data);
88 auto* metadata = SlotSpanMetadata::FromSlotStart(slot_start);
89 EncodedNextFreelistEntry::EmplaceAndInitForTest(slot_start, metadata, 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 opts.aligned_alloc = PartitionOptions::kAllowed;
105 PartitionRoot root(opts);
106 root.UncapEmptySlotSpanMemoryForTesting();
107
108 uintptr_t* zero_vector = reinterpret_cast<uintptr_t*>(
109 root.Alloc<AllocFlags::kZeroFill>(100 * sizeof(uintptr_t), ""));
110 ASSERT_TRUE(zero_vector);
111 // Pointer to the middle of an existing allocation.
112 uintptr_t* to_corrupt = zero_vector + 20;
113
114 const size_t kAllocSize = 100;
115 void* data = root.Alloc(kAllocSize);
116 void* data2 = root.Alloc(kAllocSize);
117 root.Free(data2);
118 root.Free(data);
119
120 EncodedNextFreelistEntry::EmplaceAndInitForTest(root.ObjectToSlotStart(data),
121 to_corrupt, true);
122
123 #if BUILDFLAG(USE_FREESLOT_BITMAP)
124 // This part crashes with freeslot bitmap because it detects freelist
125 // corruptions, which is rather desirable behavior.
126 EXPECT_DEATH_IF_SUPPORTED(root.Alloc(kAllocSize), "");
127 #else
128 // Next allocation is what was in
129 // root->bucket->active_slot_span_head->freelist_head, so not the corrupted
130 // pointer.
131 void* new_data = root.Alloc(kAllocSize);
132 ASSERT_EQ(new_data, data);
133
134 // Not crashing, because a zeroed area is a "valid" freelist entry.
135 void* new_data2 = root.Alloc(kAllocSize);
136 // Now we have a pointer to the middle of an existing allocation.
137 EXPECT_EQ(new_data2, to_corrupt);
138 #endif // BUILDFLAG(USE_FREESLOT_BITMAP)
139 }
140 #endif // !BUILDFLAG(IS_ANDROID)
141
142 } // namespace
143 } // namespace partition_alloc::internal
144
145 #endif // !defined(MEMORY_TOOL_REPLACES_ALLOCATOR)
146