#include "partition_alloc/buildflags.h"
#if PA_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 …
#define ASAN_BRP_MANUAL_ANALYSIS …
#define ASAN_BRP_NOT_PROTECTED …
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");
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;
(*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);
[[maybe_unused]] AsanStruct* ptr = protected_ptr;
}
TEST_F(AsanBackupRefPtrTest, Extraction) {
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
AsanStruct* ptr1 = protected_ptr;
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;
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;
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);
}
TEST_F(AsanBackupRefPtrTest, UserPoisoned) {
AsanStruct* ptr = new AsanStruct;
__asan_poison_memory_region(ptr, sizeof(AsanStruct));
[[maybe_unused]] raw_ptr<AsanStruct> protected_ptr1 =
ptr;
delete ptr;
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) {
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
auto ptr_callback = base::BindOnce(
[](AsanStruct* ptr) {
ptr->func();
},
protected_ptr);
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;
auto safe_callback = base::BindOnce(
[](AsanStruct* ptr) {
ptr->func();
},
base::Unretained(protected_ptr));
auto safe_nested_inner_callback = base::BindOnce(
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
std::move(inner_callback).Run();
ASSERT_TRUE(false);
},
base::Unretained(protected_ptr),
base::BindOnce(
[](AsanStruct* inner_ptr) {
inner_ptr->func();
},
base::Unretained(protected_ptr2)));
auto safe_nested_outer_callback = base::BindOnce(
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
std::move(inner_callback).Run();
outer_ptr->func();
},
base::Unretained(protected_ptr),
base::BindOnce(
[](AsanStruct* inner_ptr) {
},
base::Unretained(protected_ptr2)));
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;
auto unsafe_callback = base::BindOnce(
[](uintptr_t address) {
AsanStruct* ptr = reinterpret_cast<AsanStruct*>(address);
ptr->func();
},
reinterpret_cast<uintptr_t>(protected_ptr.get()));
auto unsafe_nested_inner_callback = base::BindOnce(
[](AsanStruct* outer_ptr, base::OnceClosure inner_callback) {
std::move(inner_callback).Run();
NOTREACHED();
},
base::Unretained(protected_ptr),
base::BindOnce(
[](uintptr_t inner_address) {
AsanStruct* inner_ptr =
reinterpret_cast<AsanStruct*>(inner_address);
inner_ptr->func();
},
reinterpret_cast<uintptr_t>(protected_ptr.get())));
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);
outer_ptr->func();
},
reinterpret_cast<uintptr_t>(protected_ptr.get()),
base::BindOnce(
[](AsanStruct* inner_ptr) {
},
base::Unretained(protected_ptr)));
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) {
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
auto callback = base::BindRepeating(
[](AsanStruct* ptr) {
[[maybe_unused]] raw_ptr<AsanStruct> copy_ptr = ptr;
},
base::Unretained(protected_ptr));
callback.Run();
delete protected_ptr.get();
protected_ptr = nullptr;
EXPECT_DEATH_IF_SUPPORTED(std::move(callback).Run(), kAsanBrp_Instantiation);
}
TEST_F(AsanBackupRefPtrTest, BoundReferences) {
auto ptr = ::std::make_unique<AsanStruct>();
auto no_crash_callback = base::BindOnce(
[](AsanStruct& ref) {
},
std::reference_wrapper(*ptr));
auto callback = base::BindOnce(
[](AsanStruct& ref) {
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) {
raw_ptr<AsanStruct> protected_ptr = new AsanStruct;
delete protected_ptr.get();
auto ptr_callback = base::BindOnce(
[](AsanStruct* ptr) {
},
protected_ptr);
}
}
#endif