blob: 3a42db751b25d580817d03b1ce0b99f2aaf9a7b8 [file] [log] [blame]
// 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)