// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/allocator/partition_alloc_support.h" #include #include #include #include "base/allocator/buildflags.h" #include "base/allocator/partition_alloc_features.h" #include "base/allocator/partition_allocator/dangling_raw_ptr_checks.h" #include "base/allocator/partition_allocator/partition_alloc_config.h" #include "base/feature_list.h" #include "base/test/scoped_feature_list.h" #include "build/build_config.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace allocator { using testing::AllOf; using testing::HasSubstr; #if BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) TEST(PartitionAllocSupportTest, ProposeSyntheticFinchTrials_BRPAndPCScan) { for (bool pcscan_enabled : {false, true}) { test::ScopedFeatureList pcscan_scope; std::vector empty_list = {}; std::vector pcscan_list = { features::kPartitionAllocPCScanBrowserOnly}; pcscan_scope.InitWithFeatures(pcscan_enabled ? pcscan_list : empty_list, pcscan_enabled ? empty_list : pcscan_list); #if !defined(PA_ALLOW_PCSCAN) pcscan_enabled = false; #endif std::string brp_expectation; std::string pcscan_expectation; { test::ScopedFeatureList brp_scope; brp_scope.InitWithFeatures({}, {features::kPartitionAllocBackupRefPtr}); brp_expectation = "Unavailable"; #if BUILDFLAG(USE_BACKUP_REF_PTR) brp_expectation = pcscan_enabled ? "Ignore_PCScanIsOn" : "Ignore_NoGroup"; #endif pcscan_expectation = "Unavailable"; #if defined(PA_ALLOW_PCSCAN) pcscan_expectation = pcscan_enabled ? "Enabled" : "Disabled"; #endif auto trials = ProposeSyntheticFinchTrials(); auto group_iter = trials.find("BackupRefPtr_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, brp_expectation); group_iter = trials.find("PCScan_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation); group_iter = trials.find("PCScan_Effective_Fallback"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation); } { test::ScopedFeatureList brp_scope; brp_scope.InitAndEnableFeatureWithParameters( features::kPartitionAllocBackupRefPtr, {}); brp_expectation = "Unavailable"; #if BUILDFLAG(USE_BACKUP_REF_PTR) brp_expectation = pcscan_enabled ? "Ignore_PCScanIsOn" #if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) : "EnabledPrevSlot_BrowserOnly"; #else : "EnabledBeforeAlloc_BrowserOnly"; #endif // BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) #endif // BUILDFLAG(USE_BACKUP_REF_PTR) pcscan_expectation = "Unavailable"; #if defined(PA_ALLOW_PCSCAN) #if BUILDFLAG(USE_BACKUP_REF_PTR) pcscan_expectation = "Ignore_BRPIsOn"; #else pcscan_expectation = pcscan_enabled ? "Enabled" : "Disabled"; #endif // BUILDFLAG(USE_BACKUP_REF_PTR) #endif // defined(PA_ALLOW_PCSCAN) auto trials = ProposeSyntheticFinchTrials(); auto group_iter = trials.find("BackupRefPtr_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, brp_expectation); group_iter = trials.find("PCScan_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation); group_iter = trials.find("PCScan_Effective_Fallback"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation); } const std::string kEnabledMode = #if BUILDFLAG(PUT_REF_COUNT_IN_PREVIOUS_SLOT) "PrevSlot_"; #else "BeforeAlloc_"; #endif const std::vector> kModes = { {"disabled", "Disabled"}, {"enabled", "Enabled" + kEnabledMode}, {"disabled-but-2-way-split", "DisabledBut2WaySplit_"}, {"disabled-but-3-way-split", "DisabledBut3WaySplit_"}}; const std::vector> kProcesses = { {"browser-only", "BrowserOnly"}, {"browser-and-renderer", "BrowserAndRenderer"}, {"non-renderer", "NonRenderer"}, {"all-processes", "AllProcesses"}}; for (auto mode : kModes) { for (auto process_set : kProcesses) { test::ScopedFeatureList brp_scope; brp_scope.InitAndEnableFeatureWithParameters( features::kPartitionAllocBackupRefPtr, {{"brp-mode", mode.first}, {"enabled-processes", process_set.first}}); [[maybe_unused]] bool brp_truly_enabled = false; [[maybe_unused]] bool brp_nondefault_behavior = false; brp_expectation = "Unavailable"; #if BUILDFLAG(USE_BACKUP_REF_PTR) brp_expectation = pcscan_enabled ? "Ignore_PCScanIsOn" : mode.second; brp_truly_enabled = (mode.first == "enabled"); brp_nondefault_behavior = (mode.first != "disabled"); #endif // BUILDFLAG(USE_BACKUP_REF_PTR) if (brp_expectation[brp_expectation.length() - 1] == '_') { brp_expectation += process_set.second; } pcscan_expectation = "Unavailable"; std::string pcscan_expectation_fallback = "Unavailable"; #if defined(PA_ALLOW_PCSCAN) pcscan_expectation = brp_truly_enabled ? "Ignore_BRPIsOn" : (pcscan_enabled ? "Enabled" : "Disabled"); pcscan_expectation_fallback = brp_nondefault_behavior ? "Ignore_BRPIsOn" : (pcscan_enabled ? "Enabled" : "Disabled"); #endif // defined(PA_ALLOW_PCSCAN) auto trials = ProposeSyntheticFinchTrials(); auto group_iter = trials.find("BackupRefPtr_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, brp_expectation); group_iter = trials.find("PCScan_Effective"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation); group_iter = trials.find("PCScan_Effective_Fallback"); EXPECT_NE(group_iter, trials.end()); EXPECT_EQ(group_iter->second, pcscan_expectation_fallback); } } } } #endif // BUILDFLAG(USE_PARTITION_ALLOC_AS_MALLOC) // - Death tests misbehave on Android, http://crbug.com/643760. #if BUILDFLAG(ENABLE_DANGLING_RAW_PTR_CHECKS) && !BUILDFLAG(IS_ANDROID) && \ defined(GTEST_HAS_DEATH_TEST) namespace { // Install dangling raw_ptr handler and restore them when going out of scope. class ScopedInstallDanglingRawPtrChecks { public: ScopedInstallDanglingRawPtrChecks() { enabled_feature_list_.InitWithFeaturesAndParameters( {{features::kPartitionAllocDanglingPtr, {{"mode", "crash"}}}}, {/* disabled_features */}); old_detected_fn_ = partition_alloc::GetDanglingRawPtrDetectedFn(); old_dereferenced_fn_ = partition_alloc::GetDanglingRawPtrReleasedFn(); InstallDanglingRawPtrChecks(); } ~ScopedInstallDanglingRawPtrChecks() { partition_alloc::SetDanglingRawPtrDetectedFn(old_detected_fn_); partition_alloc::SetDanglingRawPtrReleasedFn(old_dereferenced_fn_); } private: test::ScopedFeatureList enabled_feature_list_; partition_alloc::DanglingRawPtrDetectedFn* old_detected_fn_; partition_alloc::DanglingRawPtrReleasedFn* old_dereferenced_fn_; }; } // namespace TEST(PartitionAllocDanglingPtrChecks, Basic) { ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks; partition_alloc::GetDanglingRawPtrDetectedFn()(42); EXPECT_DEATH( partition_alloc::GetDanglingRawPtrReleasedFn()(42), AllOf(HasSubstr("Detected dangling raw_ptr with id=0x000000000000002a:"), HasSubstr("The memory was freed at:"), HasSubstr("The dangling raw_ptr was released at:"))); } // The StackTrace buffer might run out of storage and not record where the // memory was freed. Anyway, it must still report the error. TEST(PartitionAllocDanglingPtrChecks, FreeNotRecorded) { ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks; EXPECT_DEATH( partition_alloc::GetDanglingRawPtrReleasedFn()(42), AllOf(HasSubstr("Detected dangling raw_ptr with id=0x000000000000002a:"), HasSubstr("It was not recorded where the memory was freed."), HasSubstr("The dangling raw_ptr was released at:"))); } TEST(PartitionAllocDanglingPtrChecks, DoubleDetection) { ScopedInstallDanglingRawPtrChecks scoped_install_dangling_checks; partition_alloc::GetDanglingRawPtrDetectedFn()(42); #if DCHECK_IS_ON() EXPECT_DEATH(partition_alloc::GetDanglingRawPtrDetectedFn()(42), AllOf(HasSubstr("Check failed: !entry || entry->id != id"))); #else partition_alloc::GetDanglingRawPtrDetectedFn()(42); #endif } #endif } // namespace allocator } // namespace base