454 lines
17 KiB
C++
454 lines
17 KiB
C++
// Copyright 2023 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "base/allocator/partition_allocator/partition_alloc_buildflags.h"
|
|
|
|
#if BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
|
|
|
|
#include <sanitizer/asan_interface.h>
|
|
#include <thread>
|
|
|
|
#include "base/debug/asan_service.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback.h"
|
|
#include "base/memory/raw_ptr.h"
|
|
#include "base/memory/raw_ptr_asan_service.h"
|
|
#include "base/task/thread_pool.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/test/task_environment.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
namespace base::internal {
|
|
|
|
struct AsanStruct {
|
|
int x;
|
|
|
|
void func() { ++x; }
|
|
};
|
|
|
|
#define ASAN_BRP_PROTECTED(x) "MiraclePtr Status: PROTECTED\\n.*" x
|
|
#define ASAN_BRP_MANUAL_ANALYSIS(x) \
|
|
"MiraclePtr Status: MANUAL ANALYSIS REQUIRED\\n.*" x
|
|
#define ASAN_BRP_NOT_PROTECTED(x) "MiraclePtr Status: NOT PROTECTED\\n.*" x
|
|
|
|
const char kAsanBrpProtected_Dereference[] =
|
|
ASAN_BRP_PROTECTED("dangling pointer was being dereferenced");
|
|
const char kAsanBrpProtected_Callback[] = ASAN_BRP_PROTECTED(
|
|
"crash occurred inside a callback where a raw_ptr<T> pointing to the same "
|
|
"region");
|
|
const char kAsanBrpMaybeProtected_Extraction[] = ASAN_BRP_MANUAL_ANALYSIS(
|
|
"pointer to the same region was extracted from a raw_ptr<T>");
|
|
const char kAsanBrpNotProtected_EarlyAllocation[] = ASAN_BRP_NOT_PROTECTED(
|
|
"crash occurred while accessing a region that was allocated before "
|
|
"MiraclePtr was activated");
|
|
const char kAsanBrpNotProtected_NoRawPtrAccess[] =
|
|
ASAN_BRP_NOT_PROTECTED("No raw_ptr<T> access to this region was detected");
|
|
const char kAsanBrpMaybeProtected_Race[] =
|
|
ASAN_BRP_MANUAL_ANALYSIS("\\nThe \"use\" and \"free\" threads don't match");
|
|
const char kAsanBrpMaybeProtected_ThreadPool[] =
|
|
ASAN_BRP_MANUAL_ANALYSIS("\\nThis crash occurred in the thread pool");
|
|
|
|
// Instantiation failure message format is special.
|
|
const char kAsanBrp_Instantiation[] =
|
|
"crash occurred due to an attempt to assign a dangling pointer";
|
|
|
|
#undef ASAN_BRP_PROTECTED
|
|
#undef ASAN_BRP_MANUAL_ANALYSIS
|
|
#undef ASAN_BRP_NOT_PROTECTED
|
|
|
|
class AsanBackupRefPtrTest : public testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
base::debug::AsanService::GetInstance()->Initialize();
|
|
|
|
if (!RawPtrAsanService::GetInstance().IsEnabled()) {
|
|
base::RawPtrAsanService::GetInstance().Configure(
|
|
base::EnableDereferenceCheck(true), base::EnableExtractionCheck(true),
|
|
base::EnableInstantiationCheck(true));
|
|
} else {
|
|
ASSERT_TRUE(base::RawPtrAsanService::GetInstance()
|
|
.is_dereference_check_enabled());
|
|
ASSERT_TRUE(
|
|
base::RawPtrAsanService::GetInstance().is_extraction_check_enabled());
|
|
ASSERT_TRUE(base::RawPtrAsanService::GetInstance()
|
|
.is_instantiation_check_enabled());
|
|
}
|
|
}
|
|
|
|
static void SetUpTestSuite() { early_allocation_ptr_ = new AsanStruct; }
|
|
static void TearDownTestSuite() { delete early_allocation_ptr_; }
|
|
static raw_ptr<AsanStruct> early_allocation_ptr_;
|
|
};
|
|
|
|
raw_ptr<AsanStruct> AsanBackupRefPtrTest::early_allocation_ptr_ = nullptr;
|
|
|
|
TEST_F(AsanBackupRefPtrTest, Dereference) {
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
|
|
// The four statements below should succeed.
|
|
(*protected_ptr).x = 1;
|
|
(*protected_ptr).func();
|
|
++(protected_ptr->x);
|
|
protected_ptr->func();
|
|
|
|
delete protected_ptr.get();
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED((*protected_ptr).x = 1,
|
|
kAsanBrpProtected_Dereference);
|
|
EXPECT_DEATH_IF_SUPPORTED((*protected_ptr).func(),
|
|
kAsanBrpProtected_Dereference);
|
|
EXPECT_DEATH_IF_SUPPORTED(++(protected_ptr->x),
|
|
kAsanBrpProtected_Dereference);
|
|
EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(),
|
|
kAsanBrpProtected_Dereference);
|
|
|
|
// The following statement should not trigger a dereference, so it should
|
|
// succeed without crashing even though *protected_ptr is no longer valid.
|
|
[[maybe_unused]] AsanStruct* ptr = protected_ptr;
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, Extraction) {
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
|
|
AsanStruct* ptr1 = protected_ptr; // Shouldn't crash.
|
|
ptr1->x = 0;
|
|
|
|
delete protected_ptr.get();
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(
|
|
{
|
|
AsanStruct* ptr2 = protected_ptr;
|
|
ptr2->x = 1;
|
|
},
|
|
kAsanBrpMaybeProtected_Extraction);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, Instantiation) {
|
|
AsanStruct* ptr = new AsanStruct;
|
|
|
|
raw_ptr<AsanStruct> protected_ptr1 = ptr; // Shouldn't crash.
|
|
protected_ptr1 = nullptr;
|
|
|
|
delete ptr;
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(
|
|
{ [[maybe_unused]] raw_ptr<AsanStruct> protected_ptr2 = ptr; },
|
|
kAsanBrp_Instantiation);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, InstantiationInvalidPointer) {
|
|
void* ptr1 = reinterpret_cast<void*>(0xfefefefefefefefe);
|
|
|
|
[[maybe_unused]] raw_ptr<void> protected_ptr1 = ptr1; // Shouldn't crash.
|
|
|
|
size_t shadow_scale, shadow_offset;
|
|
__asan_get_shadow_mapping(&shadow_scale, &shadow_offset);
|
|
[[maybe_unused]] raw_ptr<void> protected_ptr2 =
|
|
reinterpret_cast<void*>(shadow_offset); // Shouldn't crash.
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, UserPoisoned) {
|
|
AsanStruct* ptr = new AsanStruct;
|
|
__asan_poison_memory_region(ptr, sizeof(AsanStruct));
|
|
|
|
[[maybe_unused]] raw_ptr<AsanStruct> protected_ptr1 =
|
|
ptr; // Shouldn't crash.
|
|
|
|
delete ptr; // Should crash now.
|
|
EXPECT_DEATH_IF_SUPPORTED(
|
|
{ [[maybe_unused]] raw_ptr<AsanStruct> protected_ptr2 = ptr; },
|
|
kAsanBrp_Instantiation);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, EarlyAllocationDetection) {
|
|
raw_ptr<AsanStruct> late_allocation_ptr = new AsanStruct;
|
|
EXPECT_FALSE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
|
|
early_allocation_ptr_.get()));
|
|
EXPECT_TRUE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
|
|
late_allocation_ptr.get()));
|
|
|
|
delete late_allocation_ptr.get();
|
|
delete early_allocation_ptr_.get();
|
|
|
|
EXPECT_FALSE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
|
|
early_allocation_ptr_.get()));
|
|
EXPECT_TRUE(RawPtrAsanService::GetInstance().IsSupportedAllocation(
|
|
late_allocation_ptr.get()));
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED({ early_allocation_ptr_->func(); },
|
|
kAsanBrpNotProtected_EarlyAllocation);
|
|
EXPECT_DEATH_IF_SUPPORTED({ late_allocation_ptr->func(); },
|
|
kAsanBrpProtected_Dereference);
|
|
|
|
early_allocation_ptr_ = nullptr;
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, BoundRawPtr) {
|
|
// This test is for the handling of raw_ptr<T> type objects being passed
|
|
// directly to Bind.
|
|
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
|
|
// First create our test callbacks while `*protected_ptr` is still valid, and
|
|
// we will then invoke them after deleting `*protected_ptr`.
|
|
|
|
// `ptr` is protected in this callback, as raw_ptr<T> will be mapped to an
|
|
// UnretainedWrapper containing a raw_ptr<T> which is guaranteed to outlive
|
|
// the function call.
|
|
auto ptr_callback = base::BindOnce(
|
|
[](AsanStruct* ptr) {
|
|
// This will crash and should be detected as a protected access.
|
|
ptr->func();
|
|
},
|
|
protected_ptr);
|
|
|
|
// Now delete `*protected_ptr` and check that the callbacks we created are
|
|
// handled correctly.
|
|
delete protected_ptr.get();
|
|
protected_ptr = nullptr;
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(ptr_callback).Run(),
|
|
kAsanBrpProtected_Callback);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, BoundArgumentsProtected) {
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
raw_ptr<AsanStruct> protected_ptr2 = new AsanStruct;
|
|
|
|
// First create our test callbacks while `*protected_ptr` is still valid, and
|
|
// we will then invoke them after deleting `*protected_ptr`.
|
|
|
|
// `ptr` is protected in this callback even after `*ptr` has been deleted,
|
|
// since the allocation will be kept alive by the internal `raw_ptr<T>` inside
|
|
// base::Unretained().
|
|
auto safe_callback = base::BindOnce(
|
|
[](AsanStruct* ptr) {
|
|
// This will crash and should be detected as a protected access.
|
|
ptr->func();
|
|
},
|
|
base::Unretained(protected_ptr));
|
|
|
|
// Both `inner_ptr` and `outer_ptr` are protected in these callbacks, since
|
|
// both are bound before `*ptr` is deleted. This test is making sure that
|
|
// `inner_ptr` is treated as protected.
|
|
auto safe_nested_inner_callback = base::BindOnce(
|
|
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
|
|
std::move(inner_callback).Run();
|
|
// This will never be executed, as we will crash in inner_callback
|
|
ASSERT_TRUE(false);
|
|
},
|
|
base::Unretained(protected_ptr),
|
|
base::BindOnce(
|
|
[](AsanStruct* inner_ptr) {
|
|
// This will crash and should be detected as a protected access.
|
|
inner_ptr->func();
|
|
},
|
|
base::Unretained(protected_ptr2)));
|
|
|
|
// Both `inner_ptr` and `outer_ptr` are protected in these callbacks, since
|
|
// both are bound before `*ptr` is deleted. This test is making sure that
|
|
// `outer_ptr` is still treated as protected after `inner_callback` has run.
|
|
auto safe_nested_outer_callback = base::BindOnce(
|
|
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
|
|
std::move(inner_callback).Run();
|
|
// This will crash and should be detected as a protected access.
|
|
outer_ptr->func();
|
|
},
|
|
base::Unretained(protected_ptr),
|
|
base::BindOnce(
|
|
[](AsanStruct* inner_ptr) {
|
|
// Do nothing - we don't want to trip the protection inside the
|
|
// inner callback.
|
|
},
|
|
base::Unretained(protected_ptr2)));
|
|
|
|
// Now delete `*protected_ptr` and check that the callbacks we created are
|
|
// handled correctly.
|
|
delete protected_ptr.get();
|
|
delete protected_ptr2.get();
|
|
protected_ptr = nullptr;
|
|
protected_ptr2 = nullptr;
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(safe_callback).Run(),
|
|
kAsanBrpProtected_Callback);
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(safe_nested_inner_callback).Run(),
|
|
kAsanBrpProtected_Callback);
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(safe_nested_outer_callback).Run(),
|
|
kAsanBrpProtected_Callback);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, BoundArgumentsNotProtected) {
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
|
|
// First create our test callbacks while `*protected_ptr` is still valid, and
|
|
// we will then invoke them after deleting `*protected_ptr`.
|
|
|
|
// `ptr` is not protected in this callback after `*ptr` has been deleted, as
|
|
// integer-type bind arguments do not use an internal `raw_ptr<T>`.
|
|
auto unsafe_callback = base::BindOnce(
|
|
[](uintptr_t address) {
|
|
AsanStruct* ptr = reinterpret_cast<AsanStruct*>(address);
|
|
// This will crash and should not be detected as a protected access.
|
|
ptr->func();
|
|
},
|
|
reinterpret_cast<uintptr_t>(protected_ptr.get()));
|
|
|
|
// In this case, `outer_ptr` is protected in these callbacks, since it is
|
|
// bound before `*ptr` is deleted. We want to make sure that the access to
|
|
// `inner_ptr` is not automatically treated as protected (although it actually
|
|
// is) because we're trying to limit the protection scope to be very
|
|
// conservative here.
|
|
auto unsafe_nested_inner_callback = base::BindOnce(
|
|
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
|
|
std::move(inner_callback).Run();
|
|
// This will never be executed, as we will crash in inner_callback
|
|
NOTREACHED();
|
|
},
|
|
base::Unretained(protected_ptr),
|
|
base::BindOnce(
|
|
[](uintptr_t inner_address) {
|
|
AsanStruct* inner_ptr =
|
|
reinterpret_cast<AsanStruct*>(inner_address);
|
|
// This will crash and should be detected as maybe protected, since
|
|
// it follows an extraction operation when the outer callback is
|
|
// invoked
|
|
inner_ptr->func();
|
|
},
|
|
reinterpret_cast<uintptr_t>(protected_ptr.get())));
|
|
|
|
// In this case, `inner_ptr` is protected in these callbacks, since it is
|
|
// bound before `*ptr` is deleted. We want to make sure that the access to
|
|
// `outer_ptr` is not automatically treated as protected, since it isn't.
|
|
auto unsafe_nested_outer_callback = base::BindOnce(
|
|
[](uintptr_t outer_address, base::OnceClosure inner_callback) {
|
|
{ std::move(inner_callback).Run(); }
|
|
AsanStruct* outer_ptr = reinterpret_cast<AsanStruct*>(outer_address);
|
|
// This will crash and should be detected as maybe protected, since it
|
|
// follows an extraction operation when the inner callback is invoked.
|
|
outer_ptr->func();
|
|
},
|
|
reinterpret_cast<uintptr_t>(protected_ptr.get()),
|
|
base::BindOnce(
|
|
[](AsanStruct* inner_ptr) {
|
|
// Do nothing - we don't want to trip the protection inside the
|
|
// inner callback.
|
|
},
|
|
base::Unretained(protected_ptr)));
|
|
|
|
// Now delete `*protected_ptr` and check that the callbacks we created are
|
|
// handled correctly.
|
|
delete protected_ptr.get();
|
|
protected_ptr = nullptr;
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_callback).Run(),
|
|
kAsanBrpNotProtected_NoRawPtrAccess);
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_nested_inner_callback).Run(),
|
|
kAsanBrpMaybeProtected_Extraction);
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(unsafe_nested_outer_callback).Run(),
|
|
kAsanBrpMaybeProtected_Extraction);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, BoundArgumentsInstantiation) {
|
|
// This test is ensuring that instantiations of `raw_ptr` inside callbacks are
|
|
// handled correctly.
|
|
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
|
|
// First create our test callback while `*protected_ptr` is still valid.
|
|
auto callback = base::BindRepeating(
|
|
[](AsanStruct* ptr) {
|
|
// This will crash if `*protected_ptr` is not valid.
|
|
[[maybe_unused]] raw_ptr<AsanStruct> copy_ptr = ptr;
|
|
},
|
|
base::Unretained(protected_ptr));
|
|
|
|
// It is allowed to create a new `raw_ptr<T>` inside a callback while
|
|
// `*protected_ptr` is still valid.
|
|
callback.Run();
|
|
|
|
delete protected_ptr.get();
|
|
protected_ptr = nullptr;
|
|
|
|
// It is not allowed to create a new `raw_ptr<T>` inside a callback once
|
|
// `*protected_ptr` is no longer valid.
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(callback).Run(), kAsanBrp_Instantiation);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, BoundReferences) {
|
|
auto ptr = ::std::make_unique<AsanStruct>();
|
|
|
|
// This test is ensuring that reference parameters inside callbacks are
|
|
// handled correctly.
|
|
|
|
// We should not crash during unwrapping a reference parameter if the
|
|
// parameter is not accessed inside the callback.
|
|
auto no_crash_callback = base::BindOnce(
|
|
[](AsanStruct& ref) {
|
|
// There should be no crash here as we don't access ref.
|
|
},
|
|
std::reference_wrapper(*ptr));
|
|
|
|
// `ref` is protected in this callback even after `*ptr` has been deleted,
|
|
// since the allocation will be kept alive by the internal `raw_ref<T>` inside
|
|
// base::UnretainedRefWrapper().
|
|
auto callback = base::BindOnce(
|
|
[](AsanStruct& ref) {
|
|
// This will crash and should be detected as protected
|
|
ref.func();
|
|
},
|
|
std::reference_wrapper(*ptr));
|
|
|
|
ptr.reset();
|
|
|
|
std::move(no_crash_callback).Run();
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(std::move(callback).Run(),
|
|
kAsanBrpProtected_Callback);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, FreeOnAnotherThread) {
|
|
auto ptr = ::std::make_unique<AsanStruct>();
|
|
raw_ptr<AsanStruct> protected_ptr = ptr.get();
|
|
|
|
std::thread thread([&ptr] { ptr.reset(); });
|
|
thread.join();
|
|
|
|
EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(), kAsanBrpMaybeProtected_Race);
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, AccessOnThreadPoolThread) {
|
|
auto ptr = ::std::make_unique<AsanStruct>();
|
|
raw_ptr<AsanStruct> protected_ptr = ptr.get();
|
|
|
|
test::TaskEnvironment env;
|
|
RunLoop run_loop;
|
|
|
|
ThreadPool::PostTaskAndReply(
|
|
FROM_HERE, {}, base::BindLambdaForTesting([&ptr, &protected_ptr] {
|
|
ptr.reset();
|
|
EXPECT_DEATH_IF_SUPPORTED(protected_ptr->func(),
|
|
kAsanBrpMaybeProtected_ThreadPool);
|
|
}),
|
|
base::BindLambdaForTesting([&run_loop]() { run_loop.Quit(); }));
|
|
run_loop.Run();
|
|
}
|
|
|
|
TEST_F(AsanBackupRefPtrTest, DanglingUnretained) {
|
|
// The test should finish without crashing.
|
|
|
|
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
|
|
delete protected_ptr.get();
|
|
|
|
auto ptr_callback = base::BindOnce(
|
|
[](AsanStruct* ptr) {
|
|
// Do nothing - we only check the behavior of `BindOnce` in this test.
|
|
},
|
|
protected_ptr);
|
|
}
|
|
|
|
} // namespace base::internal
|
|
|
|
#endif // BUILDFLAG(USE_ASAN_BACKUP_REF_PTR)
|