Chrome · Actor › Actor
CVE-2026-106199
Authorization Error in Actor (Actor)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/actor/actor_keyed_service.cc |
modified | |
ifchrome/browser/actor/actor_task.cc |
modified | |
TestTabStatechrome/browser/actor/actor_test_util.cc |
modified | |
ifchrome/browser/actor/actor_test_util.cc |
modified | |
ifchrome/browser/actor/tools/load_and_extract_content_tool.cc |
modified | |
ifchrome/browser/actor/tools/page_tool.cc |
modified | |
ifchrome/browser/actor/tools/tab_management_tool.cc |
modified | |
ifchrome/browser/actor/tools/tool.cc |
modified |
Files Changed
chrome/browser/actor/tools/window_management_tool.ccchrome/browser/actor/ui/actor_ui_state_manager_unittest.ccchrome/browser/actor/actor_test_util.ccchrome/browser/actor/tools/tool.ccchrome/browser/actor/actor_keyed_service.ccchrome/browser/actor/actor_task.hcomponents/actor/public/mojom/actor_types.mojomchrome/browser/actor/ui/task_list_bubble/actor_task_list_bubble_unittest.ccchrome/browser/actor/tools/tool_controller.ccchrome/browser/actor/tools/tool_controller.hchrome/browser/history/history_tab_helper_unittest.ccchrome/browser/actor/tools/wait_tool.ccchrome/browser/actor/tools/window_management_tool.hchrome/browser/actor/actor_keyed_service_unittest.ccchrome/browser/actor/ui/actor_ui_tab_controller_unittest.ccchrome/browser/actor/tools/load_and_extract_content_tool.ccchrome/browser/actor/tools/tool.htools/metrics/histograms/metadata/actor/enums.xmlchrome/browser/password_manager/password_manager_critical_action_logger_unittest.ccchrome/browser/actor/tools/page_tool.ccchrome/browser/actor/tools/tab_management_tool_browsertest.ccchrome/browser/actor/actor_keyed_service_browsertest.ccchrome/browser/actor/tools/tab_management_tool.ccchrome/browser/actor/actor_task.ccchrome/browser/actor/actor_task_unittest.cc
Patch
From 8d12fea02a097cdabadbd7c84b45fc63e6b7b908 Mon Sep 17 00:00:00 2001 From: mark a. foltz <[email protected]> Date: Sat, 05 Sep 2026 17:50:21 -0700 Subject: [PATCH] [actor] Enforce strict profile validation for ActorTask and tools This change addresses cross-profile security vulnerabilities in the Actor subsystem (Fix 1 and Fix 3): 1. ActorTask tab validation (Fix 1): - Updates ActorTask::CheckCrossProfileAndLog to fail closed if the tab does not exist or if tab->GetProfile() != GetProfile(). - Compares profiles directly via tab->GetProfile() rather than tab->GetContents()->GetBrowserContext(), ensuring tabs without an attached WebContents are properly validated. - Reorders checks in ActorTask::AddTab so that existence and profile validation are performed before checking controlled_tabs_. - Adds unit and browser tests verifying that unissued handles, non-existent tabs, and cross-profile tabs are rejected. 2. Tool & Keyed Service Hardening (Fix 3): - Centralizes target tab existence and profile validation in ToolController::CreateToolAndValidate and ToolController::Invoke using Tool::GetTargetTab(). This eliminates boilerplate checks across individual tools and prevents tools from accidentally skipping validation. - Fixes WaitTool::GetTargetTab() to return observe_tab_handle_. - Removes redundant ValidateTab() calls from individual tab tools (AttemptFormFillingTool, AttemptLoginTool, NavigateTool, PageTool, TabManagementTool), and removes Tool::ValidateTab(). - Retains per-tool window validation (ValidateBrowserWindow and ValidateWindowId) for window-targeting tools. - Updates WindowManagementTool::CheckCrossProfile to fail closed if the browser window is null or belongs to a different profile. - Updates LoadAndExtractContentTool to validate that the target window matches the task profile in both Validate() and Invoke(). - Hardens ActorKeyedService::CreateActorTab to reject cross-profile initiator tabs with an error (nullptr) instead of falling back. Adds browser test CreateActorTabRejectsCrossProfileInitiatorTab. - Hardens ActorKeyedService::RequestTabObservation to defensively reject cross-profile tabs. 3. Error reporting: - Adds a new result code kActionTargetCrossProfile which is returned if there is a mismatch between a tool target and the task's profile. Fixed: 551177608,517703787,502497790,502768228 Change-Id: I9f3e9d6840b46bf0d4052cabe192be9192f787ad Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8338389 Commit-Queue: Mark Foltz <[email protected]> Reviewed-by: Siddhartha S <[email protected]> Cr-Commit-Position: refs/heads/main@{#1692990} --- diff --git a/chrome/browser/actor/actor_keyed_service.cc b/chrome/browser/actor/actor_keyed_service.cc index 790564ce..5f6f248 100644 --- a/chrome/browser/actor/actor_keyed_service.cc +++ b/chrome/browser/actor/actor_keyed_service.cc @@ -266,6 +266,15 @@ BrowserWindowInterface* window_for_new_tab = nullptr; tabs::TabInterface* initiator_tab = initiator_tab_handle.Get(); + if (initiator_tab && initiator_tab->GetProfile() != profile_.get()) { + GetJournal().Log( + GURL(), task_id, "CreateActorTab", + JournalDetailsBuilder() + .AddError("Initiator tab belongs to a different profile") + .Build()); + std::move(callback).Run(nullptr); + return; + } // Special case: if the initiator tab is the NTP, no need to create a new // tab, reuse it. @@ -339,18 +348,17 @@ #endif // If the initiating tab is still live, create the new tab in the same window. - if (initiator_tab) { - if (initiator_tab->IsInNormalWindow()) { - window_for_new_tab = initiator_tab->GetBrowserWindowInterface(); - if (window_for_new_tab) { - GetJournal().Log(GURL(), task_id, "CreateActorTab", - JournalDetailsBuilder() - .Add("Using initiator_tab's window", - window_for_new_tab->GetSessionID().id()) - .Build()); - } + // (Cross-profile initiator tabs were already rejected above.) + if (initiator_tab && initiator_tab->IsInNormalWindow()) { + window_for_new_tab = initiator_tab->GetBrowserWindowInterface(); + if (window_for_new_tab) { + GetJournal().Log(GURL(), task_id, "CreateActorTab", + JournalDetailsBuilder() + .Add("Using initiator_tab's window", + window_for_new_tab->GetSessionID().id()) + .Build()); } - } else { + } else if (!initiator_tab) { // TODO(b/482430429): Figure out how to proceed from just a window ID on // Android. #if !BUILDFLAG(IS_ANDROID) @@ -358,11 +366,17 @@ // task initiation). window_for_new_tab = BrowserWindowInterface::FromSessionID(initiator_window_id); - GetJournal().Log( - GURL(), task_id, "CreateActorTab", - JournalDetailsBuilder() - .Add("Using initiator_window", initiator_window_id.id()) - .Build()); + if (window_for_new_tab && + window_for_new_tab->GetProfile() != profile_.get()) { + window_for_new_tab = nullptr; + } + if (window_for_new_tab) { + GetJournal().Log( + GURL(), task_id, "CreateActorTab", + JournalDetailsBuilder() + .Add("Using initiator_window", initiator_window_id.id()) + .Build()); + } #endif } @@ -594,6 +608,18 @@ screenshot_collection_options, base::OnceCallback<void(TabObservationResult)> callback) { TRACE_EVENT0("actor", "ActorKeyedService::RequestTabObservation"); + if (tab.GetProfile() != profile_.get()) { + journal_.Log(GURL(), task_id, "RequestTabObservation", + JournalDetailsBuilder() + .AddError("Cross-profile tab observation denied") + .Build()); + std::move(callback).Run( + base::unexpected(page_content_annotations::FetchPageContextErrorDetails{ + .error_code = page_content_annotations::FetchPageContextError:: + kPageContextNotEligible, + .message = "Cross-profile tab observation denied"})); + return; + } const GURL& last_committed_url = tab.GetContents()->GetLastCommittedURL(); auto journal_entry = journal_.CreatePendingAsyncEntry( last_committed_url, task_id, MakeBrowserTrackUUID(task_id), diff --git a/chrome/browser/actor/actor_task.cc b/chrome/browser/actor/actor_task.cc index 005a0da84..12695b91 100644 --- a/chrome/browser/actor/actor_task.cc +++ b/chrome/browser/actor/actor_task.cc @@ -625,6 +625,17 @@ : mojom::ActionResultCode::kTaskWentAway))); return; } + tabs::TabInterface* tab = tab_handle.Get(); + if (CheckCrossProfileAndLog(tab, tab_handle, "ActorTask::AddTab")) { + base::SequencedTaskRunner::GetCurrentDefault()->PostTask( + FROM_HERE, + base::BindOnce( + std::move(callback), + MakeResult(tab ? mojom::ActionResultCode::kActionTargetCrossProfile + : mojom::ActionResultCode::kTabWentAway))); + return; + } + if (controlled_tabs_.contains(tab_handle)) { last_actuated_tab_ = tab_handle; base::SequencedTaskRunner::GetCurrentDefault()->PostTask( @@ -636,15 +647,6 @@ GURL(), id(), "ActorTask::AddTab", JournalDetailsBuilder().Add("tab_id", tab_handle.raw_value()).Build()); - if (CheckCrossProfileAndLog(tab_handle.Get(), tab_handle, - "ActorTask::AddTab")) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), - MakeResult(mojom::ActionResultCode::kTaskWentAway))); - return; - } - controlled_tabs_.emplace( tab_handle, std::make_unique<ActorControlledTabState>(this, stop_task_on_detach)); @@ -715,15 +717,6 @@ } tabs::TabInterface* tab = tab_handle.Get(); - if (!tab) { - journal_->Log(GURL(), id(), "ObserveTabOnce", - JournalDetailsBuilder() - .Add("tab_id", tab_handle.raw_value()) - .AddError("Tab is gone") - .Build()); - return; - } - if (CheckCrossProfileAndLog(tab, tab_handle, "ObserveTabOnce")) { return; } @@ -973,12 +966,12 @@ bool ActorTask::CheckCrossProfileAndLog(tabs::TabInterface* tab, tabs::TabHandle tab_handle, std::string_view method_name) { - if (tab && tab->GetContents() && - tab->GetContents()->GetBrowserContext() != GetProfile()) { + if (!tab || tab->GetProfile() != GetProfile()) { journal_->Log(GURL(), id(), method_name, JournalDetailsBuilder() .Add("tab_id", tab_handle.raw_value()) - .AddError("Cross-profile access denied") + .AddError(!tab ? "Tab does not exist" + : "Cross-profile access denied") .Build()); return true; } diff --git a/chrome/browser/actor/actor_task.h b/chrome/browser/actor/actor_task.h index e090eb58..9e79083 100644 --- a/chrome/browser/actor/actor_task.h +++ b/chrome/browser/actor/actor_task.h @@ -367,8 +367,8 @@ void DidContentsExitActorControl(ActorControlledTabState* state, content::WebContents* contents); - // Returns true if the tab belongs to a different profile than the task, - // and logs an error to the journal. + // Returns true if the tab does not exist or belongs to a different profile + // than the task, and logs an error to the journal. bool CheckCrossProfileAndLog(tabs::TabInterface* tab, tabs::TabHandle tab_handle, std::string_view method_name); diff --git a/chrome/browser/actor/actor_test_util.cc b/chrome/browser/actor/actor_test_util.cc index 93cdf71..603cc66 100644 --- a/chrome/browser/actor/actor_test_util.cc +++ b/chrome/browser/actor/actor_test_util.cc @@ -38,6 +38,7 @@ #include "chrome/browser/actor/tools/translate_page_tool_request.h" #include "chrome/browser/actor/tools/type_tool_request.h" #include "chrome/browser/actor/tools/wait_tool_request.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/common/actor.mojom.h" #include "chrome/common/actor/action_result.h" #include "chrome/common/actor/actor_constants.h" @@ -980,6 +981,9 @@ TestTabState::TestTabState(content::WebContents* web_contents) { if (web_contents) { ON_CALL(tab, GetContents).WillByDefault(::testing::Return(web_contents)); + ON_CALL(tab, GetProfile) + .WillByDefault(::testing::Return( + Profile::FromBrowserContext(web_contents->GetBrowserContext()))); } ON_CALL(tab, RegisterWillDetach) .WillByDefault([this](tabs::TabInterface::WillDetach callback) { diff --git a/chrome/browser/actor/tools/load_and_extract_content_tool.cc b/chrome/browser/actor/tools/load_and_extract_content_tool.cc index 68d745ba..0ec51902 100644 --- a/chrome/browser/actor/tools/load_and_extract_content_tool.cc +++ b/chrome/browser/actor/tools/load_and_extract_content_tool.cc @@ -352,6 +352,14 @@ return; } + mojom::ActionResultPtr window_result = ValidateWindowId(window_id_); + if (!IsOk(*window_result)) { + journal().Log(GURL::EmptyGURL(), task_id(), + "LoadAndExtractContentTool::WindowValidationFailed", {}); + PostResponseTask(std::move(overall_callback), std::move(window_result)); + return; + } + // TODO(b/478282022): If any URL is invalid, we could fail immediately instead // of waiting for all URLs to return. base::RepeatingCallback<void(mojom::ActionResultPtr)> per_url_callback = @@ -376,17 +384,16 @@ GURL::EmptyGURL(), task_id(), "LoadAndExtractContentTool::Invoke", JournalDetailsBuilder().Add("url_count", per_tab_state_.size()).Build()); - BrowserWindowInterface* browser_window_interface = - BrowserWindowInterface::FromSessionID(window_id_); - - if (!browser_window_interface) { + mojom::ActionResultPtr window_result = ValidateWindowId(window_id_); + if (!IsOk(*window_result)) { journal().Log(GURL::EmptyGURL(), task_id(), - "LoadAndExtractContentTool::WindowWentAway", {}); - PostResponseTask(std::move(invoke_callback_), - MakeResult(mojom::ActionResultCode::kWindowWentAway)); + "LoadAndExtractContentTool::WindowValidationFailed", {}); + PostResponseTask(std::move(invoke_callback_), std::move(window_result)); return; } + BrowserWindowInterface* browser_window_interface = + BrowserWindowInterface::FromSessionID(window_id_); const GURL about_blank(url::kAboutBlankURL); for (size_t i = 0; i < per_tab_state_.size(); ++i) { PerTabState& per_tab_state = per_tab_state_.at(i); diff --git a/chrome/browser/actor/tools/page_tool.cc b/chrome/browser/actor/tools/page_tool.cc index 8113d4bb..0f449c2 100644 --- a/chrome/browser/actor/tools/page_tool.cc +++ b/chrome/browser/actor/tools/page_tool.cc @@ -260,15 +260,6 @@ return; } - TabInterface* tab = request_->GetTabHandle().Get(); - if (!tab) { - base::SequencedTaskRunner::GetCurrentDefault()->PostTask( - FROM_HERE, - base::BindOnce(std::move(callback), - MakeResult(mojom::ActionResultCode::kTabWentAway))); - return; - } - RenderFrameHost* frame = FindTargetLocalRootFrame(request_->GetTabHandle(), request_->GetTarget()); if (!frame) { @@ -279,6 +270,7 @@ return; } + TabInterface* tab = request_->GetTabHandle().Get(); const optimization_guide::proto::AnnotatedPageContent* last_observation = nullptr; if (auto* tab_data = ActorTabData::From(tab)) { diff --git a/chrome/browser/actor/tools/tab_management_tool.cc b/chrome/browser/actor/tools/tab_management_tool.cc index 5e366276..c3f1de7 100644 --- a/chrome/browser/actor/tools/tab_management_tool.cc +++ b/chrome/browser/actor/tools/tab_management_tool.cc @@ -44,6 +44,14 @@ TabManagementTool::~TabManagementTool() = default; void TabManagementTool::Validate(ToolCallback callback) { + if (action_ == kCreate) { + if (window_id_.has_value()) { + PostResponseTask( + std::move(callback), + ValidateWindowId(SessionID::FromSerializedValue(window_id_.value()))); + return; + } + } PostResponseTask(std::move(callback), MakeOkResult()); } @@ -51,29 +59,16 @@ callback_ = std::move(callback); CHECK(window_id_.has_value() || target_tab_.has_value()); - if (target_tab_.has_value() && !target_tab_->Get()) { - PostResponseTask(std::move(callback_), - MakeResult(mojom::ActionResultCode::kTabWentAway)); - return; - } - BrowserWindowInterface* browser_window_interface = window_id_.has_value() ? BrowserWindowInterface::FromSessionID( SessionID::FromSerializedValue(window_id_.value())) : target_tab_.value().Get()->GetBrowserWindowInterface(); - if (!browser_window_interface) { - PostResponseTask(std::move(callback_), - MakeResult(mojom::ActionResultCode::kWindowWentAway)); - return; - } - - if (browser_window_interface->GetProfile() != &tool_delegate().GetProfile()) { - PostResponseTask(std::move(callback_), - MakeResult(mojom::ActionResultCode::kWindowWentAway, - /*requires_page_stabilization=*/false, - "Cross-profile access denied.")); + mojom::ActionResultPtr window_result = + ValidateBrowserWindow(browser_window_interface); + if (!IsOk(*window_result)) { + PostResponseTask(std::move(callback_), std::move(window_result)); return; } diff --git a/chrome/browser/actor/tools/tool.cc b/chrome/browser/actor/tools/tool.cc index 1e8325c..1bb680e 100644 --- a/chrome/browser/actor/tools/tool.cc +++ b/chrome/browser/actor/tools/tool.cc @@ -4,8 +4,12 @@ #include "chrome/browser/actor/tools/tool.h" +#include "build/build_config.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser_window/public/browser_window_interface.h" #include "chrome/common/actor/action_result.h" #include "components/actor/core/aggregated_journal.h" +#include "components/tabs/public/tab_interface.h" namespace actor { @@ -39,4 +43,26 @@ std::move(callback).Run(std::move(result)); } +#if !BUILDFLAG(IS_ANDROID) +mojom::ActionResultPtr Tool::ValidateBrowserWindow( + const BrowserWindowInterface* browser) const { + if (!browser) { + return MakeResult(mojom::ActionResultCode::kWindowWentAway, ... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/actor/actor_keyed_service_browsertest.cc b/chrome/browser/actor/actor_keyed_service_browsertest.cc
index 97ef41e..84ba8bb4 100644
--- a/chrome/browser/actor/actor_keyed_service_browsertest.cc
+++ b/chrome/browser/actor/actor_keyed_service_browsertest.cc
@@ -20,11 +20,13 @@
#include "chrome/browser/actor/tools/navigate_tool_request.h"
#include "chrome/browser/optimization_guide/browser_test_util.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/create_browser_window.h"
#include "chrome/common/actor.mojom.h"
#include "chrome/common/actor/action_result.h"
#include "chrome/common/chrome_features.h"
+#include "chrome/common/url_constants.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "chrome/test/base/platform_browser_test.h"
#if !BUILDFLAG(IS_ANDROID)
@@ -480,7 +482,7 @@
auto result = future.Take();
ASSERT_TRUE(result);
- EXPECT_EQ(result->code, mojom::ActionResultCode::kTaskWentAway);
+ EXPECT_EQ(result->code, mojom::ActionResultCode::kActionTargetCrossProfile);
browser2->GetWindow()->Close();
}
@@ -510,6 +512,115 @@
browser2->GetWindow()->Close();
}
+
+IN_PROC_BROWSER_TEST_F(ActorKeyedServiceBrowserTest,
+ AddTabRejectsUnissuedTabHandle) {
+ TaskId task_id = actor_keyed_service()->CreateTask(
+ TestTaskSourceInfo(), NoEnterprisePolicyChecker());
+ ActorTask* task = actor_keyed_service()->GetTask(task_id);
+
+ tabs::TabHandle unissued_handle(99999);
+ base::test::TestFuture<mojom::ActionResultPtr> future;
+ task->AddTab(unissued_handle, /*stop_task_on_detach=*/true,
+ future.GetCallback());
+
+ auto result = future.Take();
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->code, mojom::ActionResultCode::kTabWentAway);
+ EXPECT_FALSE(task->HasTab(unissued_handle));
+ EXPECT_FALSE(task->GetTabs().contains(unissued_handle));
+}
+
+IN_PROC_BROWSER_TEST_F(ActorKeyedServiceBrowserTest,
+ CreateActorTabRejectsCrossProfileInitiatorWindow) {
+ TaskId task_id = actor_keyed_service()->CreateTask(
+ TestTaskSourceInfo(), NoEnterprisePolicyChecker());
+
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ base::FilePath profile_path =
+ profile_manager->GenerateNextProfileDirectoryPath();
+ Profile& profile2 =
+ profiles::testing::CreateProfileSync(profile_manager, profile_path);
+
+ BrowserWindowInterface* browser2 = CreateBrowserWindow(
+ BrowserWindowCreateParams(&profile2, /*from_user_gesture=*/true));
+ chrome::NewTab(browser2, NewTabTypes::kNoUserAction);
+ const int profile2_tab_count_before = browser2->GetTabStripModel()->count();
+
+ base::test::TestFuture<tabs::TabInterface*> future;
+ actor_keyed_service()->CreateActorTab(
+ task_id, /*open_in_background=*/false,
+ /*initiator_tab_handle=*/tabs::TabHandle::Null(),
+ browser2->GetSessionID(), future.GetCallback());
+
+ tabs::TabInterface* new_tab = future.Take();
+ ASSERT_NE(new_tab, nullptr);
+ EXPECT_EQ(new_tab->GetProfile(), GetProfile());
+ EXPECT_EQ(profile2_tab_count_before, browser2->GetTabStripModel()->count());
+
+ browser2->GetWindow()->Close();
+}
+
+IN_PROC_BROWSER_TEST_F(ActorKeyedServiceBrowserTest,
+ CreateActorTabRejectsCrossProfileInitiatorTab) {
+ TaskId task_id = actor_keyed_service()->CreateTask(
+ TestTaskSourceInfo(), NoEnterprisePolicyChecker());
+
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ base::FilePath profile_path =
+ profile_manager->GenerateNextProfileDirectoryPath();
+ Profile& profile2 =
+ profiles::testing::CreateProfileSync(profile_manager, profile_path);
+
+ BrowserWindowInterface* browser2 = CreateBrowserWindow(
+ BrowserWindowCreateParams(&profile2, /*from_user_gesture=*/true));
+ chrome::NewTab(browser2, NewTabTypes::kNoUserAction);
+ tabs::TabInterface* tab2 = browser2->GetActiveTabInterface();
+ ASSERT_NE(tab2, nullptr);
+ ASSERT_TRUE(content::NavigateToURL(tab2->GetContents(),
+ GURL(chrome::kChromeUINewTabURL)));
+ ASSERT_TRUE(search::IsNTPURL(
+ tab2->GetContents()->GetPrimaryMainFrame()->GetLastCommittedURL()));
+ const int profile2_tab_count_before = browser2->GetTabStripModel()->count();
+
+ base::test::TestFuture<tabs::TabInterface*> future;
+ actor_keyed_service()->CreateActorTab(
+ task_id, /*open_in_background=*/false, tab2->GetHandle(),
+ browser2->GetSessionID(), future.GetCallback());
+
+ tabs::TabInterface* new_tab = future.Take();
+ EXPECT_EQ(new_tab, nullptr);
+ EXPECT_EQ(profile2_tab_count_before, browser2->GetTabStripModel()->count());
+
+ browser2->GetWindow()->Close();
+}
+
+IN_PROC_BROWSER_TEST_F(ActorKeyedServiceBrowserTest,
+ RequestTabObservationRejectsCrossProfileTab) {
+ TaskId task_id = actor_keyed_service()->CreateTask(
+ TestTaskSourceInfo(), NoEnterprisePolicyChecker());
+
+ ProfileManager* profile_manager = g_browser_process->profile_manager();
+ base::FilePath profile_path =
+ profile_manager->GenerateNextProfileDirectoryPath();
+ Profile& profile2 =
+ profiles::testing::CreateProfileSync(profile_manager, profile_path);
+
+ BrowserWindowInterface* browser2 = CreateBrowserWindow(
+ BrowserWindowCreateParams(&profile2, /*from_user_gesture=*/true));
+ chrome::NewTab(browser2, NewTabTypes::kNoUserAction);
+ tabs::TabInterface* tab2 = browser2->GetActiveTabInterface();
+
+ base::test::TestFuture<ActorKeyedService::TabObservationResult> future;
+ actor_keyed_service()->RequestTabObservation(
+ *tab2, task_id, /*screenshot_collection_options=*/std::nullopt,
+ future.GetCallback());
+
+ auto result = future.Take();
+ EXPECT_FALSE(result.has_value());
+
+ browser2->GetWindow()->Close();
+}
#endif
IN_PROC_BROWSER_TEST_F(ActorKeyedServiceBrowserTest,
diff --git a/chrome/browser/actor/actor_keyed_service_unittest.cc b/chrome/browser/actor/actor_keyed_service_unittest.cc
index 6ac8506..2c01c51 100644
--- a/chrome/browser/actor/actor_keyed_service_unittest.cc
+++ b/chrome/browser/actor/actor_keyed_service_unittest.cc
@@ -29,6 +29,7 @@
#include "components/actor/core/actor_switches.h"
#include "components/actor/core/task_source_info.h"
#include "components/actor/public/mojom/actor_types.mojom.h"
+#include "components/tabs/public/mock_tab_interface.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -73,6 +74,12 @@
TestingProfile* profile() { return profile_.get(); }
+ std::unique_ptr<tabs::MockTabInterface> CreateMockTab() {
+ auto mock_tab = std::make_unique<tabs::MockTabInterface>();
+ ON_CALL(*mock_tab, GetProfile).WillByDefault(testing::Return(profile()));
+ return mock_tab;
+ }
+
void RunTasksUntilIdle() { task_environment_.RunUntilIdle(); }
protected:
@@ -102,9 +109,11 @@
NoEnterprisePolicyChecker());
// Add a tab to the task
+ auto mock_tab = CreateMockTab();
+ const tabs::TabHandle tab_handle = mock_tab->GetHandle();
base::WeakPtr<ActorTask> task = actor_service->GetTask(id)->GetWeakPtr();
base::RunLoop loop;
- task->AddTab(tabs::TabHandle(123),
+ task->AddTab(tab_handle,
/*stop_task_on_detach=*/true,
base::BindLambdaForTesting([&](mojom::ActionResultPtr result) {
EXPECT_TRUE(IsOk(*result));
@@ -112,8 +121,8 @@
}));
loop.Run();
- EXPECT_TRUE(task->IsActingOnTab(tabs::TabHandle(123)));
- EXPECT_TRUE(task->HasTab(tabs::TabHandle(123)));
+ EXPECT_TRUE(task->IsActingOnTab(tab_handle));
+ EXPECT_TRUE(task->HasTab(tab_handle));
actor_service->StopTask(id, ActorTask::StoppedReason::kTaskComplete);
// Tasks are deleted asynchronously.
@@ -150,7 +159,8 @@
base::WeakPtr<ActorTask> task = actor_service->GetTask(id)->GetWeakPtr();
ASSERT_TRUE(task);
- const tabs::TabHandle tab_handle(123);
+ auto mock_tab = CreateMockTab();
+ const tabs::TabHandle tab_handle = mock_tab->GetHandle();
// Pause the task and try to add a tab.
task->Pause(/*from_actor=*/true);
@@ -184,7 +194,8 @@
base::WeakPtr<ActorTask> task = actor_service->GetTask(id)->GetWeakPtr();
ASSERT_TRUE(task);
- const tabs::TabHandle tab_handle(123);
+ auto mock_tab = CreateMockTab();
+ const tabs::TabHandle tab_handle = mock_tab->GetHandle();
{
base::test::TestFuture<mojom::ActionResultPtr> future;
@@ -277,7 +288,8 @@
TEST_F(ActorKeyedServiceTest, InitialTabAssociationOnCreate) {
auto* actor_service = ActorKeyedService::Get(profile());
- const tabs::TabHandle tab_handle(123);
+ auto mock_tab = CreateMockTab();
+ const tabs::TabHandle tab_handle = mock_tab->GetHandle();
auto options = webui::mojom::TaskOptions::New();
options->actuation_tab_id = tab_handle.raw_value();
diff --git a/chrome/browser/actor/actor_task_unittest.cc b/chrome/browser/actor/actor_task_unittest.cc
index 0f34e40..a449ea0 100644
--- a/chrome/browser/actor/actor_task_unittest.cc
+++ b/chrome/browser/actor/actor_task_unittest.cc
@@ -132,7 +132,10 @@
.Times(1);
}
- void AddTabAndVerify(tabs::TabInterface& tab) {
+ void AddTabAndVerify(tabs::MockTabInterface& tab) {
+ if (!tab.GetProfile()) {
+ ON_CALL(tab, GetProfile).WillByDefault(::testing::Return(profile_.get()));
+ }
ExpectTabAddedNotification(tab.GetHandle());
AddTabToTask(tab, *task_);
EXPECT_TRUE(task_->HasTab(tab.GetHandle()));
@@ -183,10 +186,25 @@
service->ResetForTesting();
}
+ std::unique_ptr<tabs::MockTabInterface> CreateCrossProfileMockTab(
+ Profile* other_profile = nullptr) {
+ if (!other_profile) {
+ if (!other_profile_) {
+ other_profile_ = TestingProfile::Builder().Build();
+ }
+ other_profile = other_profile_.get();
+ }
+ auto mock_tab = std::make_unique<tabs::MockTabInterface>();
+ ON_CALL(*mock_tab, GetProfile)
+ .WillByDefault(::testing::Return(other_profile));
+ return mock_tab;
+ }
+
protected:
content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<TestingProfile> profile_;
+ std::unique_ptr<TestingProfile> other_profile_;
MockActorTaskDelegate mock_delegate_;
raw_ptr<ActorTask> task_;
raw_ptr<ui::MockUiEventDispatcher> mock_ui_event_dispatcher_;
@@ -774,6 +792,61 @@
histograms.ExpectTotalCount("Actor.Task.Count.Completed.Other", 0);
}
+TEST_F(ActorTaskTest, AddTab_RejectsNonExistentTab) {
+ tabs::TabHandle non_existent_handle(99999);
+ base::test::TestFuture<mojom::ActionResultPtr> add_tab_future;
+ task_->AddTab(non_existent_handle, /*stop_task_on_detach=*/true,
+ add_tab_future.GetCallback());
+ auto result = add_tab_future.Take();
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->code, mojom::ActionResultCode::kTabWentAway);
+ EXPECT_FALSE(task_->HasTab(non_existent_handle));
+ EXPECT_FALSE(task_->GetTabs().contains(non_existent_handle));
+}
+
+TEST_F(ActorTaskTest, AddTab_RejectsCrossProfileTab) {
+ std::unique_ptr<tabs::MockTabInterface> cross_profile_tab =
+ CreateCrossProfileMockTab();
+
+ base::test::TestFuture<mojom::ActionResultPtr> add_tab_future;
+ task_->AddTab(cross_profile_tab->GetHandle(), /*stop_task_on_detach=*/true,
+ add_tab_future.GetCallback());
+ auto result = add_tab_future.Take();
+ ASSERT_TRUE(result);
+ EXPECT_EQ(result->code, mojom::ActionResultCode::kActionTargetCrossProfile);
+ EXPECT_FALSE(task_->HasTab(cross_profile_tab->GetHandle()));
+ EXPECT_FALSE(task_->GetTabs().contains(cross_profile_tab->GetHandle()));
+}
+
+TEST_F(ActorTaskTest, AddTab_RejectsCrossProfileTabEvenWithNullContents) {
+ std::unique_ptr<tabs::MockTabInterface> cross_profile_tab =
+ CreateCrossProfileMockTab();
+ ON_CALL(*cross_profile_tab, GetContents)
+ .WillByDefault(::testing::Return(nullptr));
+
+ base::test::TestFuture<mojom::ActionResultPtr> add_tab_future;
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page