1 // Copyright 2023 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 "partition_alloc/partition_alloc_buildflags.h"
6
7 #if BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
8
9 #include <sanitizer/asan_interface.h>
10 #include <thread>
11
12 #include "base/debug/asan_service.h"
13 #include "base/functional/bind.h"
14 #include "base/functional/callback.h"
15 #include "base/memory/raw_ptr.h"
16 #include "base/memory/raw_ptr_asan_service.h"
17 #include "base/task/thread_pool.h"
18 #include "base/test/bind.h"
19 #include "base/test/task_environment.h"
20 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h"
22
23 namespace base::internal {
24
25 struct AsanStruct {
26 int x;
27
funcbase::internal::AsanStruct28 void func() { ++x; }
29 };
30
31 #define ASAN_BRP_PROTECTED(x) "MiraclePtr Status: PROTECTED\\n.*" x
32 #define ASAN_BRP_MANUAL_ANALYSIS(x) \
33 "MiraclePtr Status: MANUAL ANALYSIS REQUIRED\\n.*" x
34 #define ASAN_BRP_NOT_PROTECTED(x) "MiraclePtr Status: NOT PROTECTED\\n.*" x
35
36 const char kAsanBrpProtected_Dereference[] =
37 ASAN_BRP_PROTECTED("dangling pointer was being dereferenced");
38 const char kAsanBrpProtected_Callback[] = ASAN_BRP_PROTECTED(
39 "crash occurred inside a callback where a raw_ptr<T> pointing to the same "
40 "region");
41 const char kAsanBrpMaybeProtected_Extraction[] = ASAN_BRP_MANUAL_ANALYSIS(
42 "pointer to the same region was extracted from a raw_ptr<T>");
43 const char kAsanBrpNotProtected_EarlyAllocation[] = ASAN_BRP_NOT_PROTECTED(
44 "crash occurred while accessing a region that was allocated before "
45 "MiraclePtr was activated");
46 const char kAsanBrpNotProtected_NoRawPtrAccess[] =
47 ASAN_BRP_NOT_PROTECTED("No raw_ptr<T> access to this region was detected");
48 const char kAsanBrpMaybeProtected_Race[] =
49 ASAN_BRP_MANUAL_ANALYSIS("\\nThe \"use\" and \"free\" threads don't match");
50 const char kAsanBrpMaybeProtected_ThreadPool[] =
51 ASAN_BRP_MANUAL_ANALYSIS("\\nThis crash occurred in the thread pool");
52
53 // Instantiation failure message format is special.
54 const char kAsanBrp_Instantiation[] =
55 "crash occurred due to an attempt to assign a dangling pointer";
56
57 #undef ASAN_BRP_PROTECTED
58 #undef ASAN_BRP_MANUAL_ANALYSIS
59 #undef ASAN_BRP_NOT_PROTECTED
60
61 class AsanBackupRefPtrTest : public testing::Test {
62 protected:
SetUp()63 void SetUp() override {
64 base::debug::AsanService::GetInstance()->Initialize();
65
66 if (!RawPtrAsanService::GetInstance().IsEnabled()) {
67 base::RawPtrAsanService::GetInstance().Configure(
68 base::EnableDereferenceCheck(true), base::EnableExtractionCheck(true),
69 base::EnableInstantiationCheck(true));
70 } else {
71 ASSERT_TRUE(base::RawPtrAsanService::GetInstance()
72 .is_dereference_check_enabled());
73 ASSERT_TRUE(
74 base::RawPtrAsanService::GetInstance().is_extraction_check_enabled());
75 ASSERT_TRUE(base::RawPtrAsanService::GetInstance()
76 .is_instantiation_check_enabled());
77 }
78 }
79
SetUpTestSuite()80 static void SetUpTestSuite() { early_allocation_ptr_ = new AsanStruct; }
TearDownTestSuite()81 static void TearDownTestSuite() { delete early_allocation_ptr_; }
82 static raw_ptr<AsanStruct> early_allocation_ptr_;
83 };
84
85 raw_ptr<AsanStruct> AsanBackupRefPtrTest::early_allocation_ptr_ = nullptr;
86
TEST_F(AsanBackupRefPtrTest,Dereference)87 TEST_F(AsanBackupRefPtrTest, Dereference) {
88 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
89
90 // The four statements below should succeed.
91 (*protected_ptr).x = 1;
92 (*protected_ptr).func();
93 ++(protected_ptr->x);
94 protected_ptr->func();
95
96 delete protected_ptr.get();
97
98 EXPECT_DEATH_IF_SUPPORTED((*protected_ptr).x = 1,
99 kAsanBrpProtected_Dereference);
100 EXPECT_DEATH_IF_SUPPORTED((*protected_ptr).func(),
101 kAsanBrpProtected_Dereference);
102 EXPECT_DEATH_IF_SUPPORTED(++(protected_ptr->x),
103 kAsanBrpProtected_Dereference);
104 EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(),
105 kAsanBrpProtected_Dereference);
106
107 // The following statement should not trigger a dereference, so it should
108 // succeed without crashing even though *protected_ptr is no longer valid.
109 [[maybe_unused]] AsanStruct* ptr = protected_ptr;
110 }
111
TEST_F(AsanBackupRefPtrTest,Extraction)112 TEST_F(AsanBackupRefPtrTest, Extraction) {
113 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
114
115 AsanStruct* ptr1 = protected_ptr; // Shouldn't crash.
116 ptr1->x = 0;
117
118 delete protected_ptr.get();
119
120 EXPECT_DEATH_IF_SUPPORTED(
121 {
122 AsanStruct* ptr2 = protected_ptr;
123 ptr2->x = 1;
124 },
125 kAsanBrpMaybeProtected_Extraction);
126 }
127
TEST_F(AsanBackupRefPtrTest,Instantiation)128 TEST_F(AsanBackupRefPtrTest, Instantiation) {
129 AsanStruct* ptr = new AsanStruct;
130
131 raw_ptr<AsanStruct> protected_ptr1 = ptr; // Shouldn't crash.
132 protected_ptr1 = nullptr;
133
134 delete ptr;
135
136 EXPECT_DEATH_IF_SUPPORTED(
137 { [[maybe_unused]] raw_ptr<AsanStruct> protected_ptr2 = ptr; },
138 kAsanBrp_Instantiation);
139 }
140
TEST_F(AsanBackupRefPtrTest,InstantiationInvalidPointer)141 TEST_F(AsanBackupRefPtrTest, InstantiationInvalidPointer) {
142 void* ptr1 = reinterpret_cast<void*>(0xfefefefefefefefe);
143
144 [[maybe_unused]] raw_ptr<void> protected_ptr1 = ptr1; // Shouldn't crash.
145
146 size_t shadow_scale, shadow_offset;
147 __asan_get_shadow_mapping(&shadow_scale, &shadow_offset);
148 [[maybe_unused]] raw_ptr<void> protected_ptr2 =
149 reinterpret_cast<void*>(shadow_offset); // Shouldn't crash.
150 }
151
TEST_F(AsanBackupRefPtrTest,UserPoisoned)152 TEST_F(AsanBackupRefPtrTest, UserPoisoned) {
153 AsanStruct* ptr = new AsanStruct;
154 __asan_poison_memory_region(ptr, sizeof(AsanStruct));
155
156 [[maybe_unused]] raw_ptr<AsanStruct> protected_ptr1 =
157 ptr; // Shouldn't crash.
158
159 delete ptr; // Should crash now.
160 EXPECT_DEATH_IF_SUPPORTED(
161 { [[maybe_unused]] raw_ptr<AsanStruct> protected_ptr2 = ptr; },
162 kAsanBrp_Instantiation);
163 }
164
TEST_F(AsanBackupRefPtrTest,EarlyAllocationDetection)165 TEST_F(AsanBackupRefPtrTest, EarlyAllocationDetection) {
166 raw_ptr<AsanStruct> late_allocation_ptr = new AsanStruct;
167 EXPECT_FALSE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
168 early_allocation_ptr_.get()));
169 EXPECT_TRUE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
170 late_allocation_ptr.get()));
171
172 delete late_allocation_ptr.get();
173 delete early_allocation_ptr_.get();
174
175 EXPECT_FALSE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
176 early_allocation_ptr_.get()));
177 EXPECT_TRUE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
178 late_allocation_ptr.get()));
179
180 EXPECT_DEATH_IF_SUPPORTED({ early_allocation_ptr_->func(); },
181 kAsanBrpNotProtected_EarlyAllocation);
182 EXPECT_DEATH_IF_SUPPORTED({ late_allocation_ptr->func(); },
183 kAsanBrpProtected_Dereference);
184
185 early_allocation_ptr_ = nullptr;
186 }
187
TEST_F(AsanBackupRefPtrTest,BoundRawPtr)188 TEST_F(AsanBackupRefPtrTest, BoundRawPtr) {
189 // This test is for the handling of raw_ptr<T> type objects being passed
190 // directly to Bind.
191
192 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
193
194 // First create our test callbacks while `*protected_ptr` is still valid, and
195 // we will then invoke them after deleting `*protected_ptr`.
196
197 // `ptr` is protected in this callback, as raw_ptr<T> will be mapped to an
198 // UnretainedWrapper containing a raw_ptr<T> which is guaranteed to outlive
199 // the function call.
200 auto ptr_callback = base::BindOnce(
201 [](AsanStruct* ptr) {
202 // This will crash and should be detected as a protected access.
203 ptr->func();
204 },
205 protected_ptr);
206
207 // Now delete `*protected_ptr` and check that the callbacks we created are
208 // handled correctly.
209 delete protected_ptr.get();
210 protected_ptr = nullptr;
211
212 EXPECT_DEATH_IF_SUPPORTED(std::move(ptr_callback).Run(),
213 kAsanBrpProtected_Callback);
214 }
215
TEST_F(AsanBackupRefPtrTest,BoundArgumentsProtected)216 TEST_F(AsanBackupRefPtrTest, BoundArgumentsProtected) {
217 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
218 raw_ptr<AsanStruct> protected_ptr2 = new AsanStruct;
219
220 // First create our test callbacks while `*protected_ptr` is still valid, and
221 // we will then invoke them after deleting `*protected_ptr`.
222
223 // `ptr` is protected in this callback even after `*ptr` has been deleted,
224 // since the allocation will be kept alive by the internal `raw_ptr<T>` inside
225 // base::Unretained().
226 auto safe_callback = base::BindOnce(
227 [](AsanStruct* ptr) {
228 // This will crash and should be detected as a protected access.
229 ptr->func();
230 },
231 base::Unretained(protected_ptr));
232
233 // Both `inner_ptr` and `outer_ptr` are protected in these callbacks, since
234 // both are bound before `*ptr` is deleted. This test is making sure that
235 // `inner_ptr` is treated as protected.
236 auto safe_nested_inner_callback = base::BindOnce(
237 [](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
238 std::move(inner_callback).Run();
239 // This will never be executed, as we will crash in inner_callback
240 ASSERT_TRUE(false);
241 },
242 base::Unretained(protected_ptr),
243 base::BindOnce(
244 [](AsanStruct* inner_ptr) {
245 // This will crash and should be detected as a protected access.
246 inner_ptr->func();
247 },
248 base::Unretained(protected_ptr2)));
249
250 // Both `inner_ptr` and `outer_ptr` are protected in these callbacks, since
251 // both are bound before `*ptr` is deleted. This test is making sure that
252 // `outer_ptr` is still treated as protected after `inner_callback` has run.
253 auto safe_nested_outer_callback = base::BindOnce(
254 [](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
255 std::move(inner_callback).Run();
256 // This will crash and should be detected as a protected access.
257 outer_ptr->func();
258 },
259 base::Unretained(protected_ptr),
260 base::BindOnce(
261 [](AsanStruct* inner_ptr) {
262 // Do nothing - we don't want to trip the protection inside the
263 // inner callback.
264 },
265 base::Unretained(protected_ptr2)));
266
267 // Now delete `*protected_ptr` and check that the callbacks we created are
268 // handled correctly.
269 delete protected_ptr.get();
270 delete protected_ptr2.get();
271 protected_ptr = nullptr;
272 protected_ptr2 = nullptr;
273
274 EXPECT_DEATH_IF_SUPPORTED(std::move(safe_callback).Run(),
275 kAsanBrpProtected_Callback);
276 EXPECT_DEATH_IF_SUPPORTED(std::move(safe_nested_inner_callback).Run(),
277 kAsanBrpProtected_Callback);
278 EXPECT_DEATH_IF_SUPPORTED(std::move(safe_nested_outer_callback).Run(),
279 kAsanBrpProtected_Callback);
280 }
281
TEST_F(AsanBackupRefPtrTest,BoundArgumentsNotProtected)282 TEST_F(AsanBackupRefPtrTest, BoundArgumentsNotProtected) {
283 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
284
285 // First create our test callbacks while `*protected_ptr` is still valid, and
286 // we will then invoke them after deleting `*protected_ptr`.
287
288 // `ptr` is not protected in this callback after `*ptr` has been deleted, as
289 // integer-type bind arguments do not use an internal `raw_ptr<T>`.
290 auto unsafe_callback = base::BindOnce(
291 [](uintptr_t address) {
292 AsanStruct* ptr = reinterpret_cast<AsanStruct*>(address);
293 // This will crash and should not be detected as a protected access.
294 ptr->func();
295 },
296 reinterpret_cast<uintptr_t>(protected_ptr.get()));
297
298 // In this case, `outer_ptr` is protected in these callbacks, since it is
299 // bound before `*ptr` is deleted. We want to make sure that the access to
300 // `inner_ptr` is not automatically treated as protected (although it actually
301 // is) because we're trying to limit the protection scope to be very
302 // conservative here.
303 auto unsafe_nested_inner_callback = base::BindOnce(
304 [](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
305 std::move(inner_callback).Run();
306 // This will never be executed, as we will crash in inner_callback
307 NOTREACHED();
308 },
309 base::Unretained(protected_ptr),
310 base::BindOnce(
311 [](uintptr_t inner_address) {
312 AsanStruct* inner_ptr =
313 reinterpret_cast<AsanStruct*>(inner_address);
314 // This will crash and should be detected as maybe protected, since
315 // it follows an extraction operation when the outer callback is
316 // invoked
317 inner_ptr->func();
318 },
319 reinterpret_cast<uintptr_t>(protected_ptr.get())));
320
321 // In this case, `inner_ptr` is protected in these callbacks, since it is
322 // bound before `*ptr` is deleted. We want to make sure that the access to
323 // `outer_ptr` is not automatically treated as protected, since it isn't.
324 auto unsafe_nested_outer_callback = base::BindOnce(
325 [](uintptr_t outer_address, base::OnceClosure inner_callback) {
326 { std::move(inner_callback).Run(); }
327 AsanStruct* outer_ptr = reinterpret_cast<AsanStruct*>(outer_address);
328 // This will crash and should be detected as maybe protected, since it
329 // follows an extraction operation when the inner callback is invoked.
330 outer_ptr->func();
331 },
332 reinterpret_cast<uintptr_t>(protected_ptr.get()),
333 base::BindOnce(
334 [](AsanStruct* inner_ptr) {
335 // Do nothing - we don't want to trip the protection inside the
336 // inner callback.
337 },
338 base::Unretained(protected_ptr)));
339
340 // Now delete `*protected_ptr` and check that the callbacks we created are
341 // handled correctly.
342 delete protected_ptr.get();
343 protected_ptr = nullptr;
344
345 EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_callback).Run(),
346 kAsanBrpNotProtected_NoRawPtrAccess);
347 EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_nested_inner_callback).Run(),
348 kAsanBrpMaybeProtected_Extraction);
349 EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_nested_outer_callback).Run(),
350 kAsanBrpMaybeProtected_Extraction);
351 }
352
TEST_F(AsanBackupRefPtrTest,BoundArgumentsInstantiation)353 TEST_F(AsanBackupRefPtrTest, BoundArgumentsInstantiation) {
354 // This test is ensuring that instantiations of `raw_ptr` inside callbacks are
355 // handled correctly.
356
357 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
358
359 // First create our test callback while `*protected_ptr` is still valid.
360 auto callback = base::BindRepeating(
361 [](AsanStruct* ptr) {
362 // This will crash if `*protected_ptr` is not valid.
363 [[maybe_unused]] raw_ptr<AsanStruct> copy_ptr = ptr;
364 },
365 base::Unretained(protected_ptr));
366
367 // It is allowed to create a new `raw_ptr<T>` inside a callback while
368 // `*protected_ptr` is still valid.
369 callback.Run();
370
371 delete protected_ptr.get();
372 protected_ptr = nullptr;
373
374 // It is not allowed to create a new `raw_ptr<T>` inside a callback once
375 // `*protected_ptr` is no longer valid.
376 EXPECT_DEATH_IF_SUPPORTED(std::move(callback).Run(), kAsanBrp_Instantiation);
377 }
378
TEST_F(AsanBackupRefPtrTest,BoundReferences)379 TEST_F(AsanBackupRefPtrTest, BoundReferences) {
380 auto ptr = ::std::make_unique<AsanStruct>();
381
382 // This test is ensuring that reference parameters inside callbacks are
383 // handled correctly.
384
385 // We should not crash during unwrapping a reference parameter if the
386 // parameter is not accessed inside the callback.
387 auto no_crash_callback = base::BindOnce(
388 [](AsanStruct& ref) {
389 // There should be no crash here as we don't access ref.
390 },
391 std::reference_wrapper(*ptr));
392
393 // `ref` is protected in this callback even after `*ptr` has been deleted,
394 // since the allocation will be kept alive by the internal `raw_ref<T>` inside
395 // base::UnretainedRefWrapper().
396 auto callback = base::BindOnce(
397 [](AsanStruct& ref) {
398 // This will crash and should be detected as protected
399 ref.func();
400 },
401 std::reference_wrapper(*ptr));
402
403 ptr.reset();
404
405 std::move(no_crash_callback).Run();
406
407 EXPECT_DEATH_IF_SUPPORTED(std::move(callback).Run(),
408 kAsanBrpProtected_Callback);
409 }
410
TEST_F(AsanBackupRefPtrTest,FreeOnAnotherThread)411 TEST_F(AsanBackupRefPtrTest, FreeOnAnotherThread) {
412 auto ptr = ::std::make_unique<AsanStruct>();
413 raw_ptr<AsanStruct> protected_ptr = ptr.get();
414
415 std::thread thread([&ptr] { ptr.reset(); });
416 thread.join();
417
418 EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(), kAsanBrpMaybeProtected_Race);
419 }
420
TEST_F(AsanBackupRefPtrTest,AccessOnThreadPoolThread)421 TEST_F(AsanBackupRefPtrTest, AccessOnThreadPoolThread) {
422 auto ptr = ::std::make_unique<AsanStruct>();
423 raw_ptr<AsanStruct> protected_ptr = ptr.get();
424
425 test::TaskEnvironment env;
426 RunLoop run_loop;
427
428 ThreadPool::PostTaskAndReply(
429 FROM_HERE, {}, base::BindLambdaForTesting([&ptr, &protected_ptr] {
430 ptr.reset();
431 EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(),
432 kAsanBrpMaybeProtected_ThreadPool);
433 }),
434 base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
435 run_loop.Run();
436 }
437
TEST_F(AsanBackupRefPtrTest,DanglingUnretained)438 TEST_F(AsanBackupRefPtrTest, DanglingUnretained) {
439 // The test should finish without crashing.
440
441 raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
442 delete protected_ptr.get();
443
444 auto ptr_callback = base::BindOnce(
445 [](AsanStruct* ptr) {
446 // Do nothing - we only check the behavior of `BindOnce` in this test.
447 },
448 protected_ptr);
449 }
450
451 } // namespace base::internal
452
453 #endif // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
454