Chrome · Glic
CVE-2026-13823
UAF in Glic
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/glic/service/glic_instance_coordinator_impl.cc |
modified |
Files Changed
chrome/browser/glic/service/glic_instance_coordinator_browsertest.ccchrome/browser/glic/service/glic_instance_coordinator_impl.cc
Patch
From 394eed87683d1605c639ed37060b26afbac84491 Mon Sep 17 00:00:00 2001 From: Bryant Chandler <[email protected]> Date: Fri, 15 May 2026 17:11:30 -0700 Subject: [PATCH] Fix tab restore id matching in GlicInstanceCoordinatorImpl - In GetOrRestoreInstanceImpl, fail lookup if conversation ID matches but instance ID differs, preventing dangerous map overwrites. - In RemoveInstance, verify the pointer matches before erasing. - In CreateGlicInstance, call metrics before map insertion. Added a test case to verify the collision handling in restoration. Bug: b:513163011 Change-Id: I59f3952dff5e19e69a9475549afcf0e76a6a6964 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7850539 Reviewed-by: Dan H <[email protected]> Commit-Queue: Bryant Chandler <[email protected]> Cr-Commit-Position: refs/heads/main@{#1631655} --- diff --git a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc index b5160082..483e0ee3 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc +++ b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc @@ -17,6 +17,7 @@ #include "build/build_config.h" #include "chrome/browser/glic/fre/glic_fre_controller.h" #include "chrome/browser/glic/glic_pref_names.h" +#include "chrome/browser/glic/glic_tab_restore_data.h" #include "chrome/browser/glic/host/glic.mojom-shared.h" #include "chrome/browser/glic/host/glic.mojom.h" #include "chrome/browser/glic/host/glic_web_client_access.h" @@ -1002,6 +1003,60 @@ } IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest, + TabRestoration_ConversationIdMismatchReturnsNull) { + // Tab 1: Keep the instance alive. + CreateAndActivateTab(GURL("about:blank")); + ASSERT_OK_AND_ASSIGN(GlicInstanceImpl * instance, OpenGlicForActiveTab()); + auto instance_id = instance->id(); + + // Set a conversation ID on the instance. + const std::string kConvId = "test_conversation_id"; + auto info = mojom::ConversationInfo::New(); + info->conversation_id = kConvId; + instance->RegisterConversation(std::move(info), base::DoNothing()); + + // Create a fake restore state for a new tab. + // It will have the SAME instance ID but a DIFFERENT conversation ID. + GlicRestoredState state; + state.bound_instance.instance_id = instance_id.value(); + state.bound_instance.conversation_id = "different_conversation_id"; + state.side_panel_open = true; // Try to open side panel, should be skipped. + + // Create a WebContents manually. + std::unique_ptr<content::WebContents> web_contents = + content::WebContents::Create( + content::WebContents::CreateParams(GetProfile())); + + // Attach the restore data. + GlicTabRestoreData::CreateForWebContents(web_contents.get(), + std::move(state)); + + // Now add it to the tab strip. + auto* tab_list = GetTabListInterface(); + tabs::TabInterface* restored_tab = nullptr; + { + GlicTestTabAddedWaiter waiter(GetProfile()); + tab_list->InsertWebContentsAt(-1, std::move(web_contents), + /*should_pin=*/false, std::nullopt); + restored_tab = waiter.Wait(); + } + ASSERT_TRUE(restored_tab); + + // Verify that the restored tab is NOT bound to the instance. + EXPECT_EQ(GetInstanceForTab(restored_tab), nullptr); + + // Verify that the side panel is NOT open for the new tab. + EXPECT_OK(WaitForSidePanelState(restored_tab, + GlicSidePanelCoordinator::State::kClosed)); + + // Clean up the tab we created and wait for it to be destroyed to avoid + // race conditions during test teardown. + content::WebContentsDestroyedWatcher destroyer(restored_tab->GetContents()); + tab_list->CloseTab(restored_tab->GetHandle()); + destroyer.Wait(); +} + +IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest, TabRestoration_SidePanelClosed) { // Add a new tab so we don't close the browser when we close the tab. auto* tab = CreateAndActivateTab(GURL("about:blank")); diff --git a/chrome/browser/glic/service/glic_instance_coordinator_impl.cc b/chrome/browser/glic/service/glic_instance_coordinator_impl.cc index ccc5744f..5fadcfcf 100644 --- a/chrome/browser/glic/service/glic_instance_coordinator_impl.cc +++ b/chrome/browser/glic/service/glic_instance_coordinator_impl.cc @@ -621,10 +621,9 @@ ApplyMaxAwakeInstancesLimit(); auto instance = CreateInstanceImpl(instance_id); + instance->instance_metrics().OnInstanceCreatedWithoutWarming(); auto* instance_ptr = instance.get(); instances_[instance->id()] = std::move(instance); - // TODO(harringtond): Figure out what to do about this metric. - instance_ptr->instance_metrics().OnInstanceCreatedWithoutWarming(); return instance_ptr; } @@ -716,10 +715,16 @@ } void GlicInstanceCoordinatorImpl::RemoveInstance(GlicInstanceImpl* instance) { - if (!instances_.contains(instance->id())) { + auto it = instances_.find(instance->id()); + if (it == instances_.end()) { // This instance has already been removed, so there's no work to do. return; } + // If an entry exists for this ID, it must be the specific instance we are + // removing. We prohibit overwriting instances in the map, so a mismatch + // would indicate a logic bug or state corruption (e.g., during restoration). + CHECK_EQ(it->second.get(), instance); + OnInstanceActivationChanged(instance, false); // Remove the instance first, and then delete. This way, @@ -1005,12 +1010,30 @@ return nullptr; } - // Prioritize finding an existing instance by conversation ID, then by - // instance ID. - if (auto* instance = - !instance_info.conversation_id.empty() - ? GetInstanceImplForConversationId(instance_info.conversation_id) - : GetInstanceImplFor(instance_id)) { + GlicInstanceImpl* instance = nullptr; + if (!instance_info.conversation_id.empty()) { + instance = GetInstanceImplForConversationId(instance_info.conversation_id); + if (!instance) { + // If lookup by conversation ID failed, but an instance with this ID + // already exists, it implies an attempt to associate an existing instance + // with a different conversation ID. Once an instance is associated with a + // conversation ID, it cannot change. This indicates corrupt persisted + // data or a logic bug. Return nullptr to avoid dangerously overwriting + // the instance. + if (GetInstanceImplFor(instance_id)) { + LOG(ERROR) << "Instance restoration failed for conversation " + << instance_info.conversation_id + << ": The requested InstanceId " << instance_info.instance_id + << " already exists but is associated with a different " + "conversation."; + return nullptr; + } + } + } else { + instance = GetInstanceImplFor(instance_id); + } + + if (instance) { return instance; }
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc
index b5160082..483e0ee3 100644
--- a/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc
+++ b/chrome/browser/glic/service/glic_instance_coordinator_browsertest.cc
@@ -17,6 +17,7 @@
#include "build/build_config.h"
#include "chrome/browser/glic/fre/glic_fre_controller.h"
#include "chrome/browser/glic/glic_pref_names.h"
+#include "chrome/browser/glic/glic_tab_restore_data.h"
#include "chrome/browser/glic/host/glic.mojom-shared.h"
#include "chrome/browser/glic/host/glic.mojom.h"
#include "chrome/browser/glic/host/glic_web_client_access.h"
@@ -1002,6 +1003,60 @@
}
IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest,
+ TabRestoration_ConversationIdMismatchReturnsNull) {
+ // Tab 1: Keep the instance alive.
+ CreateAndActivateTab(GURL("about:blank"));
+ ASSERT_OK_AND_ASSIGN(GlicInstanceImpl * instance, OpenGlicForActiveTab());
+ auto instance_id = instance->id();
+
+ // Set a conversation ID on the instance.
+ const std::string kConvId = "test_conversation_id";
+ auto info = mojom::ConversationInfo::New();
+ info->conversation_id = kConvId;
+ instance->RegisterConversation(std::move(info), base::DoNothing());
+
+ // Create a fake restore state for a new tab.
+ // It will have the SAME instance ID but a DIFFERENT conversation ID.
+ GlicRestoredState state;
+ state.bound_instance.instance_id = instance_id.value();
+ state.bound_instance.conversation_id = "different_conversation_id";
+ state.side_panel_open = true; // Try to open side panel, should be skipped.
+
+ // Create a WebContents manually.
+ std::unique_ptr<content::WebContents> web_contents =
+ content::WebContents::Create(
+ content::WebContents::CreateParams(GetProfile()));
+
+ // Attach the restore data.
+ GlicTabRestoreData::CreateForWebContents(web_contents.get(),
+ std::move(state));
+
+ // Now add it to the tab strip.
+ auto* tab_list = GetTabListInterface();
+ tabs::TabInterface* restored_tab = nullptr;
+ {
+ GlicTestTabAddedWaiter waiter(GetProfile());
+ tab_list->InsertWebContentsAt(-1, std::move(web_contents),
+ /*should_pin=*/false, std::nullopt);
+ restored_tab = waiter.Wait();
+ }
+ ASSERT_TRUE(restored_tab);
+
+ // Verify that the restored tab is NOT bound to the instance.
+ EXPECT_EQ(GetInstanceForTab(restored_tab), nullptr);
+
+ // Verify that the side panel is NOT open for the new tab.
+ EXPECT_OK(WaitForSidePanelState(restored_tab,
+ GlicSidePanelCoordinator::State::kClosed));
+
+ // Clean up the tab we created and wait for it to be destroyed to avoid
+ // race conditions during test teardown.
+ content::WebContentsDestroyedWatcher destroyer(restored_tab->GetContents());
+ tab_list->CloseTab(restored_tab->GetHandle());
+ destroyer.Wait();
+}
+
+IN_PROC_BROWSER_TEST_F(GlicInstanceCoordinatorBrowserTest,
TabRestoration_SidePanelClosed) {
// Add a new tab so we don't close the browser when we close the tab.
auto* tab = CreateAndActivateTab(GURL("about:blank"));
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