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 "base/allocator/partition_alloc_support.h"
6
7 #include <array>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/allocator/partition_alloc_features.h"
13 #include "base/test/gtest_util.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/test/task_environment.h"
16 #include "build/build_config.h"
17 #include "partition_alloc/dangling_raw_ptr_checks.h"
18 #include "partition_alloc/partition_alloc_base/cpu.h"
19 #include "partition_alloc/partition_alloc_buildflags.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 namespace base::allocator {
24
25 using testing::AllOf;
26 using testing::HasSubstr;
27
TEST(PartitionAllocSupportTest,ProposeSyntheticFinchTrials_DanglingPointerDetector)28 TEST(PartitionAllocSupportTest,
29 ProposeSyntheticFinchTrials_DanglingPointerDetector) {
30 std::string dpd_group =
31 ProposeSyntheticFinchTrials()["DanglingPointerDetector"];
32
33 #if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS)
34 EXPECT_EQ(dpd_group, "Enabled");
35 #else
36 EXPECT_EQ(dpd_group, "Disabled");
37 #endif
38 }
39
40 // - Death tests misbehave on Android, http://crbug.com/643760.
41 #if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS) && !BUILDFLAG(IS_ANDROID) && \
42 defined(GTEST_HAS_DEATH_TEST)
43
44 namespace {
45
46 // Install dangling raw_ptr handler and restore them when going out of scope.
47 class ScopedInstallDanglingRawPtrChecks {
48 public:
49 struct ConstructorParams {
50 std::string mode = "crash";
51 std::string type = "all";
52 };
ScopedInstallDanglingRawPtrChecks(ConstructorParams params)53 ScopedInstallDanglingRawPtrChecks(ConstructorParams params) {
54 enabled_feature_list_.InitWithFeaturesAndParameters(
55 {{features::kPartitionAllocDanglingPtr,
56 {{"mode", params.mode}, {"type", params.type}}}},
57 {/* disabled_features */});
58
59 old_detected_fn_ = partition_alloc::GetDanglingRawPtrDetectedFn();
60 old_dereferenced_fn_ = partition_alloc::GetDanglingRawPtrReleasedFn();
61 InstallDanglingRawPtrChecks();
62 }
ScopedInstallDanglingRawPtrChecks()63 ScopedInstallDanglingRawPtrChecks()
64 : ScopedInstallDanglingRawPtrChecks(ConstructorParams{}) {}
~ScopedInstallDanglingRawPtrChecks()65 ~ScopedInstallDanglingRawPtrChecks() {
66 InstallDanglingRawPtrChecks(); // Check for leaks.
67 partition_alloc::SetDanglingRawPtrDetectedFn(old_detected_fn_);
68 partition_alloc::SetDanglingRawPtrReleasedFn(old_dereferenced_fn_);
69 }
70
71 private:
72 test::ScopedFeatureList enabled_feature_list_;
73 partition_alloc::DanglingRawPtrDetectedFn* old_detected_fn_;
74 partition_alloc::DanglingRawPtrReleasedFn* old_dereferenced_fn_;
75 };
76
77 } // namespace
78
TEST(PartitionAllocDanglingPtrChecks,Basic)79 TEST(PartitionAllocDanglingPtrChecks, Basic) {
80 EXPECT_DEATH(
81 {
82 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
83 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
84 partition_alloc::GetDanglingRawPtrReleasedFn()(42);
85 },
86 AllOf(HasSubstr("Detected dangling raw_ptr with id=0x000000000000002a:"),
87 HasSubstr("[DanglingSignature]\t"),
88 HasSubstr("The memory was freed at:"),
89 HasSubstr("The dangling raw_ptr was released at:")));
90 }
91
92 // The StackTrace buffer might run out of storage and not record where the
93 // memory was freed. Anyway, it must still report the error.
TEST(PartitionAllocDanglingPtrChecks,FreeNotRecorded)94 TEST(PartitionAllocDanglingPtrChecks, FreeNotRecorded) {
95 EXPECT_DEATH(
96 {
97 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
98 partition_alloc::GetDanglingRawPtrReleasedFn()(42);
99 },
100 AllOf(HasSubstr("Detected dangling raw_ptr with id=0x000000000000002a:"),
101 HasSubstr("[DanglingSignature]\tmissing\tmissing\t"),
102 HasSubstr("It was not recorded where the memory was freed."),
103 HasSubstr("The dangling raw_ptr was released at:")));
104 }
105
106 // TODO(https://crbug.com/1425095): Check for leaked refcount on Android.
107 #if BUILDFLAG(IS_ANDROID)
108 // Some raw_ptr might never release their refcount. Make sure this cause a
109 // crash on exit.
TEST(PartitionAllocDanglingPtrChecks,ReleaseNotRecorded)110 TEST(PartitionAllocDanglingPtrChecks, ReleaseNotRecorded) {
111 EXPECT_DEATH(
112 {
113 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
114 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
115 },
116 HasSubstr("A freed allocation is still referenced by a dangling pointer "
117 "at exit, or at test end. Leaked raw_ptr/raw_ref "
118 "could cause PartitionAlloc's quarantine memory bloat."
119 "\n\n"
120 "Memory was released on:"));
121 }
122 #endif
123
124 // Getting the same allocation reported twice in a row, without matching
125 // `DanglingRawPtrReleased` in between is unexpected. Make sure this kind of
126 // potential regression would be detected.
TEST(PartitionAllocDanglingPtrChecks,DoubleDetection)127 TEST(PartitionAllocDanglingPtrChecks, DoubleDetection) {
128 EXPECT_DCHECK_DEATH_WITH(
129 {
130 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks;
131 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
132 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
133 },
134 "Check failed: !entry \\|\\| entry->id != id");
135 }
136
137 // Free and release from two different tasks with cross task dangling pointer
138 // detection enabled.
TEST(PartitionAllocDanglingPtrChecks,CrossTask)139 TEST(PartitionAllocDanglingPtrChecks, CrossTask) {
140 BASE_EXPECT_DEATH(
141 {
142 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
143 .type = "cross_task",
144 });
145
146 base::test::TaskEnvironment task_environment;
147 task_environment.GetMainThreadTaskRunner()->PostTask(
148 FROM_HERE,
149 base::BindOnce(partition_alloc::GetDanglingRawPtrDetectedFn(), 42));
150 task_environment.GetMainThreadTaskRunner()->PostTask(
151 FROM_HERE,
152 base::BindOnce(partition_alloc::GetDanglingRawPtrReleasedFn(), 42));
153
154 task_environment.RunUntilIdle();
155 },
156 AllOf(HasSubstr("Detected dangling raw_ptr with id=0x000000000000002a:"),
157 HasSubstr("[DanglingSignature]\t"),
158 HasSubstr("The memory was freed at:"),
159 HasSubstr("The dangling raw_ptr was released at:")));
160 }
161
TEST(PartitionAllocDanglingPtrChecks,CrossTaskIgnoredFailuresClearsCache)162 TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoredFailuresClearsCache) {
163 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
164 .type = "cross_task",
165 });
166
167 base::test::TaskEnvironment task_environment;
168 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
169 partition_alloc::GetDanglingRawPtrReleasedFn()(42);
170 task_environment.GetMainThreadTaskRunner()->PostTask(
171 FROM_HERE,
172 base::BindOnce(partition_alloc::GetDanglingRawPtrReleasedFn(), 42));
173 task_environment.RunUntilIdle();
174 }
175
TEST(PartitionAllocDanglingPtrChecks,CrossTaskIgnoresNoTask)176 TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoresNoTask) {
177 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
178 .type = "cross_task",
179 });
180
181 partition_alloc::GetDanglingRawPtrDetectedFn()(42);
182 partition_alloc::GetDanglingRawPtrReleasedFn()(42);
183 }
184
TEST(PartitionAllocDanglingPtrChecks,CrossTaskIgnoresSameTask)185 TEST(PartitionAllocDanglingPtrChecks, CrossTaskIgnoresSameTask) {
186 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
187 .type = "cross_task",
188 });
189
190 base::test::TaskEnvironment task_environment;
191 task_environment.GetMainThreadTaskRunner()->PostTask(
192 FROM_HERE, base::BindOnce([]() {
193 partition_alloc::GetDanglingRawPtrDetectedFn()(37);
194 partition_alloc::GetDanglingRawPtrReleasedFn()(37);
195 }));
196 task_environment.RunUntilIdle();
197 }
198
TEST(PartitionAllocDanglingPtrChecks,CrossTaskNoFreeConsideredCrossTask)199 TEST(PartitionAllocDanglingPtrChecks, CrossTaskNoFreeConsideredCrossTask) {
200 ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks({
201 .type = "cross_task",
202 });
203 partition_alloc::GetDanglingRawPtrReleasedFn()(42);
204 }
205
TEST(PartitionAllocDanglingPtrChecks,ExtractDanglingPtrSignatureMacStackTrace)206 TEST(PartitionAllocDanglingPtrChecks,
207 ExtractDanglingPtrSignatureMacStackTrace) {
208 const std::string stack_trace_output =
209 "0 lib_1 0x0000000115fdfa12 base::F1(**) + 18\r\n"
210 "1 lib_1 0x0000000115ec0043 base::F2() + 19\r\n"
211 "2 lib_1 0x000000011601fb01 "
212 "allocator_shim::internal::PartitionFree(foo) + 13265\r\n"
213 "3 lib_1 0x0000000114831027 base::F3(bar) + 42\r\n"
214 "4 lib_2 0x00000001148eae35 base::F4() + 437\r\n";
215 EXPECT_EQ("base::F3(bar)",
216 PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
217 stack_trace_output));
218 }
219
TEST(PartitionAllocDanglingPtrChecks,ExtractDanglingPtrSignatureMacTaskTrace)220 TEST(PartitionAllocDanglingPtrChecks, ExtractDanglingPtrSignatureMacTaskTrace) {
221 const std::string task_trace_output =
222 "Task trace:\r\n"
223 "0 lib_1 0x00000001161fd431 base::F1() + 257\r\n"
224 "1 lib_1 0x0000000115a49404 base::F2() + 68\r\n";
225 EXPECT_EQ("base::F1()",
226 PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
227 task_trace_output));
228 }
229
TEST(PartitionAllocDanglingPtrChecks,ExtractDanglingPtrSignatureWindowsStackTrace)230 TEST(PartitionAllocDanglingPtrChecks,
231 ExtractDanglingPtrSignatureWindowsStackTrace) {
232 const std::string stack_trace_output =
233 "\tbase::F1 [0x055643C3+19] (o:\\base\\F1.cc:329)\r\n"
234 "\tallocator_shim::internal::PartitionFree [0x0648F87B+5243] "
235 "(o:\\path.cc:441)\r\n"
236 "\t_free_base [0x0558475D+29] (o:\\file_path.cc:142)\r\n"
237 "\tbase::F2 [0x04E5B317+23] (o:\\base\\F2.cc:91)\r\n"
238 "\tbase::F3 [0x04897800+544] (o:\\base\\F3.cc:638)\r\n";
239 EXPECT_EQ("base::F2",
240 PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
241 stack_trace_output));
242 }
243
TEST(PartitionAllocDanglingPtrChecks,ExtractDanglingPtrSignatureWindowsTaskTrace)244 TEST(PartitionAllocDanglingPtrChecks,
245 ExtractDanglingPtrSignatureWindowsTaskTrace) {
246 const std::string task_trace_output =
247 "Task trace:\r\n"
248 "\tbase::F1 [0x049068A3+813] (o:\\base\\F1.cc:207)\r\n"
249 "\tbase::F2 [0x0490614C+192] (o:\\base\\F2.cc:116)\r\n";
250 EXPECT_EQ("base::F1",
251 PartitionAllocSupport::ExtractDanglingPtrSignatureForTests(
252 task_trace_output));
253 }
254
255 #endif
256
257 #if BUILDFLAG(HAS_MEMORY_TAGGING)
TEST(PartitionAllocSupportTest,ProposeSyntheticFinchTrials_MemoryTaggingDogfood)258 TEST(PartitionAllocSupportTest,
259 ProposeSyntheticFinchTrials_MemoryTaggingDogfood) {
260 {
261 test::ScopedFeatureList scope;
262 scope.InitWithFeatures({}, {features::kPartitionAllocMemoryTagging});
263
264 auto trials = ProposeSyntheticFinchTrials();
265
266 auto group_iter = trials.find("MemoryTaggingDogfood");
267 EXPECT_EQ(group_iter, trials.end());
268 }
269
270 {
271 test::ScopedFeatureList scope;
272 scope.InitWithFeatures({features::kPartitionAllocMemoryTagging}, {});
273
274 auto trials = ProposeSyntheticFinchTrials();
275
276 std::string expectation =
277 partition_alloc::internal::base::CPU::GetInstanceNoAllocation()
278 .has_mte()
279 ? "Enabled"
280 : "Disabled";
281 auto group_iter = trials.find("MemoryTaggingDogfood");
282 EXPECT_NE(group_iter, trials.end());
283 EXPECT_EQ(group_iter->second, expectation);
284 }
285 }
286 #endif // BUILDFLAG(HAS_MEMORY_TAGGING)
287
288 } // namespace base::allocator
289