diff --git a/chrome/updater/policy/service_unittest.cc b/chrome/updater/policy/service_unittest.cc index 7bb5ee9e8ea0b..dedf8395cf28c 100644 --- a/chrome/updater/policy/service_unittest.cc +++ b/chrome/updater/policy/service_unittest.cc @@ -13,6 +13,9 @@ #include #include +#include +#include + #include "base/files/file_util.h" #include "base/json/json_string_value_serializer.h" #include "base/json/values_util.h" @@ -310,6 +313,64 @@ class PolicyServiceTest : public ::testing::Test { base::test::TaskEnvironment environment_; }; +// POC: Demonstrates the unsynchronized read-vs-rebuild race on +// PolicyService::PolicyManagers::managers_. On Windows, the reader is a COM RPC +// pool thread executing IPolicyStatus::get_* (via PolicyService::QueryPolicy -> +// policy_managers_->managers()), and the writer is the main sequence executing +// FetchPoliciesDone -> ResetDeviceManagementManager -> InitializeManagersVector +// (managers_.clear() + rebuild). PolicyManagers has no lock; the only guard is a +// DCHECK_CALLED_ON_VALID_SEQUENCE which is a no-op in release. Under ASAN this +// produces heap-use-after-free on the freed PolicyManagerInterface during the +// vector copy-ctor (scoped_refptr::AddRef on freed object) and/or during the +// virtual call in the iteration loop. +TEST(PolicyManagersRace, ReadVsResetDeviceManagementManager) { + // Construct PolicyManagers with no external constants: only the + // DefaultValuesPolicyManager is present. Then install a "DM" manager that + // will be swapped out on every writer iteration. + PolicyService::PolicyManagers pm(/*external_constants=*/nullptr); + pm.ResetDeviceManagementManager(GetDefaultValuesPolicyManager()); + + std::atomic stop{false}; + + // READER: simulates the COM RPC thread executing IPolicyStatus::get_* -> + // PolicyService::QueryPolicy(). managers() returns the vector by value + // (copy-constructing each scoped_refptr), then we virtual-call through each + // element exactly as QueryPolicy() does (HasActiveDevicePolicies/source). + auto reader = [&] { + while (!stop.load(std::memory_order_relaxed)) { + for (const scoped_refptr& m : pm.managers()) { + if (m) { + (void)m->HasActiveDevicePolicies(); + (void)m->source(); + } + } + } + }; + + // WRITER: simulates FetchPoliciesDone() on the main sequence. Each iteration + // installs a *fresh* DM manager, so the previous one's refcount drops + // 2 -> 1 (dm_policy_manager_ = new) -> 0 (managers_.clear()) -> delete, + // while the reader may still be mid-copy / mid-iteration. + auto writer = [&] { + while (!stop.load(std::memory_order_relaxed)) { + pm.ResetDeviceManagementManager(GetDefaultValuesPolicyManager()); + } + }; + + std::thread r1(reader); + std::thread r2(reader); + std::thread w(writer); + + // Spin for a bounded time; ASAN aborts on first UAF. + for (int i = 0; i < 200000 && !stop.load(); ++i) { + pm.ResetDeviceManagementManager(GetDefaultValuesPolicyManager()); + } + stop.store(true); + r1.join(); + r2.join(); + w.join(); +} + TEST_F(PolicyServiceTest, DefaultPolicyValue) { auto policy_service = CreatePolicyServiceForTesting({GetDefaultValuesPolicyManager()});