Low watch CVSS 4.3 chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Gemini Live in Chrome
DescriptionInappropriate implementation in Gemini Live in Chrome
ComponentGemini Live in Chrome
Bug ClassLogic Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker407791462
Fix commit2d484049e881 (chromium/src) +179/-1
CWECWE-79 (Cross-site scripting)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:L/A:N
CISA KEVNot listed
CreditedAlesandro Ortiz
Reported2025-04-02 · bounty $10000 (per vendor advisory)
Fix landed2025-06-17 (public git)
Disclosed2025-08-05
Patch gap49 days — fix public in source before the advisory

Background

GLIC widget
the Gemini Live in Chrome floating window (views::Widget from GlicWindowController::GetGlicWidget()) that, like PiP, can float on top of other windows.
`PictureInPictureOcclusionTracker`
the browser component that tracks floating (picture-in-picture) widgets and important dialogs so it can react when a floating widget occludes a security dialog (e.g., disabling dialog buttons).
`GlicWindowController::StateObserver` / `PanelStateChanged`
the observer hook that reports GLIC panel state transitions, including detachment into a floating window (mojom::PanelState_Kind::kDetached).
Occlusion of important dialogs
the security concern that a floating window covering a permission or other important dialog could trick the user, which the tracker exists to mitigate.

Root Cause Analysis

GLIC widgets float on top of other windows and therefore share the picture-in-picture occlusion risk, but the PictureInPictureOcclusionTracker was never told about the GLIC widget, so when a floating GLIC window occluded an important dialog (such as a permission dialog) the browser did not apply its normal occlusion mitigations. This is an inappropriate-implementation/logic gap rather than a memory-safety bug: a whole class of floating window was outside the tracker’s coverage.

The fix adds GlicOcclusionNotifier, a GlicWindowController::StateObserver owned by GlicKeyedService, whose PanelStateChanged calls tracker->OnPictureInPictureWidgetOpened(glic_widget) when the panel becomes kDetached (floating) and tracker->RemovePictureInPictureWidget(glic_widget) otherwise. It guards against a missing tracker or missing glic_widget with early returns, so the GLIC widget is registered for occlusion tracking exactly while it floats, closing the coverage gap.

Key insight
The floating GLIC widget was never registered with the PictureInPictureOcclusionTracker, so its occlusion of important dialogs went unmitigated; the fix adds a GlicOcclusionNotifier that adds the widget to the tracker when detached and removes it otherwise.

Attack Path

  1. Float the GLIC window The GLIC (Gemini Live) panel is detached into a floating, always-on-top widget.
  2. Surface an important dialog An attacker page or flow causes an important dialog (e.g., a permission prompt) to appear underneath the floating GLIC widget.
  3. Occlude without mitigation Pre-patch, because the GLIC widget was not tracked by PictureInPictureOcclusionTracker, the browser did not apply its occlusion handling (such as disabling the occluded dialog’s buttons), leaving the dialog obscured.
  4. User acts on the obscured dialog The user may interact with a security dialog they cannot correctly perceive; concrete exploitation such as tricking the user into granting a permission is (inferred), as the patch demonstrates only the missing tracking and its remediation.

Impact Assessment

An attacker gains the ability to have a floating GLIC widget occlude an important browser security dialog without the normal occlusion mitigations applying, an inappropriate-implementation issue that can impair the user’s ability to safely act on that dialog. Preconditions are a detached (floating) GLIC widget and an important dialog appearing beneath it. The advisory reports this as inappropriate implementation in Gemini Live in Chrome; no exploited-in-the-wild status is stated, and any escalation such as coerced permission grants is (inferred).

Changed Functions

FunctionChangeNotes
contextual_cueing_service_
chrome/browser/glic/glic_keyed_service.cc
modified
GlicActorController
chrome/browser/glic/glic_keyed_service.h
modified
GlicEnabling
chrome/browser/glic/glic_keyed_service.h
modified
GlicMetrics
chrome/browser/glic/glic_keyed_service.h
modified
GlicOcclusionNotifier
chrome/browser/glic/glic_keyed_service.h
modified
GlicProfileManager
chrome/browser/glic/glic_keyed_service.h
modified
GlicScreenshotCapturer
chrome/browser/glic/glic_keyed_service.h
modified
GlicSharingManagerImpl
chrome/browser/glic/glic_keyed_service.h
modified
if
chrome/browser/glic/glic_occlusion_notifier.cc
modified
GlicOcclusionNotifier
chrome/browser/glic/glic_occlusion_notifier.h
modified

Files Changed

  • chrome/browser/glic/BUILD.gn
  • chrome/browser/glic/glic_keyed_service.cc
  • chrome/browser/glic/glic_keyed_service.h
  • chrome/browser/glic/glic_occlusion_notifier.cc
  • chrome/browser/glic/glic_occlusion_notifier.h
  • chrome/browser/glic/test_support/interactive_glic_test.h

Audit Directions

  • Untracked always-on-top surfaces
    Enumerate every floating/always-on-top window type (PiP, GLIC, custom overlays) and confirm each is registered with PictureInPictureOcclusionTracker (or an equivalent) so none can occlude important dialogs unmitigated.
  • State-conditional registration
    Review observers that add/remove a widget from a tracker based on state transitions, ensuring the add and remove branches cover every relevant state (detached vs. attached/hidden) with no leaked or missing registration.
  • New floating UI features
    When new floating UI is introduced, audit whether it inherits the security invariants (occlusion handling, input protection) already applied to picture-in-picture windows.
From 2d484049e88157419b77ae3e68f4a8ff598a242d Mon Sep 17 00:00:00 2001
From: Tommy Steimel <steimel@chromium.org>
Date: Tue, 17 Jun 2025 12:16:40 -0700
Subject: [PATCH] Track floating glic widgets to handle when they occlude dialogs

The PictureInPictureOcclusionTracker keeps track of picture-in-picture
widgets and important dialogs (e.g. permission dialogs) to ensure that
when a picture-in-picture window occludes an important dialog, we
properly handle it (e.g. by disabling the dialog buttons).

GLIC widgets also float on top of other windows, and so face some of
the same security issues as picture-in-picture windows.

This CL tells the PictureInPictureOcclusionTracker to also track the
GLIC widget (when it's floating) so we can handle when it is occluding
important dialogs.

Bug: 407791462
Change-Id: Ic1e67d193d6f61db0d12a342da867b1e89475ae8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6639092
Reviewed-by: Justin DeWitt <dewittj@chromium.org>
Reviewed-by: Frank Liberato <liberato@chromium.org>
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1475149}
---

diff --git a/chrome/browser/glic/BUILD.gn b/chrome/browser/glic/BUILD.gn
index c816509..57bd9f9 100644
--- a/chrome/browser/glic/BUILD.gn
+++ b/chrome/browser/glic/BUILD.gn
@@ -94,6 +94,8 @@
     "glic_metrics.cc",
     "glic_metrics_provider.cc",
     "glic_metrics_provider.h",
+    "glic_occlusion_notifier.cc",
+    "glic_occlusion_notifier.h",
     "glic_pref_names.cc",
     "glic_profile_manager.cc",
     "glic_settings_util.cc",
@@ -150,6 +152,7 @@
     "//chrome/browser/glic/media",
     "//chrome/browser/lifetime:termination_notification",
     "//chrome/browser/media/webrtc",
+    "//chrome/browser/picture_in_picture",
     "//chrome/browser/profiles:profile_util",
     "//chrome/browser/profiles/keep_alive",
     "//chrome/browser/resources/glic:resources",
diff --git a/chrome/browser/glic/glic_keyed_service.cc b/chrome/browser/glic/glic_keyed_service.cc
index 61ec92c..c5cd105 100644
--- a/chrome/browser/glic/glic_keyed_service.cc
+++ b/chrome/browser/glic/glic_keyed_service.cc
@@ -23,6 +23,7 @@
 #include "chrome/browser/glic/glic_enums.h"
 #include "chrome/browser/glic/glic_keyed_service_factory.h"
 #include "chrome/browser/glic/glic_metrics.h"
+#include "chrome/browser/glic/glic_occlusion_notifier.h"
 #include "chrome/browser/glic/glic_pref_names.h"
 #include "chrome/browser/glic/glic_profile_manager.h"
 #include "chrome/browser/glic/host/auth_controller.h"
@@ -105,6 +106,8 @@
       auth_controller_(std::make_unique<AuthController>(profile,
                                                         identity_manager,
                                                         /*use_for_fre=*/false)),
+      occlusion_notifier_(
+          std::make_unique<GlicOcclusionNotifier>(*window_controller_)),
       contextual_cueing_service_(contextual_cueing_service) {
   CHECK(GlicEnabling::IsProfileEligible(Profile::FromBrowserContext(profile)));
   host_->Initialize(window_controller_.get());
diff --git a/chrome/browser/glic/glic_keyed_service.h b/chrome/browser/glic/glic_keyed_service.h
index e6eb733..35002f61 100644
--- a/chrome/browser/glic/glic_keyed_service.h
+++ b/chrome/browser/glic/glic_keyed_service.h
@@ -41,6 +41,7 @@
 class GlicActorController;
 class GlicEnabling;
 class GlicMetrics;
+class GlicOcclusionNotifier;
 class GlicProfileManager;
 class GlicScreenshotCapturer;
 class GlicSharingManagerImpl;
@@ -243,6 +244,7 @@
   std::unique_ptr<AuthController> auth_controller_;
   std::unique_ptr<GlicActorController> actor_controller_;
   std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
+  std::unique_ptr<GlicOcclusionNotifier> occlusion_notifier_;
   base::OnceCallback<void()> preload_callback_;
 
   // Unowned
diff --git a/chrome/browser/glic/glic_occlusion_notifier.cc b/chrome/browser/glic/glic_occlusion_notifier.cc
new file mode 100644
index 0000000..eb044a6
--- /dev/null
+++ b/chrome/browser/glic/glic_occlusion_notifier.cc
@@ -0,0 +1,44 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/glic/glic_occlusion_notifier.h"
+
+#include "chrome/browser/glic/widget/glic_widget.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
+
+namespace glic {
+
+GlicOcclusionNotifier::GlicOcclusionNotifier(
+    GlicWindowController& window_controller)
+    : window_controller_(window_controller) {
+  window_controller_->AddStateObserver(this);
+}
+
+GlicOcclusionNotifier::~GlicOcclusionNotifier() {
+  window_controller_->RemoveStateObserver(this);
+}
+
+void GlicOcclusionNotifier::PanelStateChanged(
+    const mojom::PanelState& panel_state,
+    Browser*) {
+  PictureInPictureOcclusionTracker* tracker =
+      PictureInPictureWindowManager::GetInstance()->GetOcclusionTracker();
+  if (!tracker) {
+    return;
+  }
+
+  views::Widget* glic_widget = window_controller_->GetGlicWidget();
+  if (!glic_widget) {
+    return;
+  }
+
+  if (panel_state.kind == mojom::PanelState_Kind::kDetached) {
+    tracker->OnPictureInPictureWidgetOpened(glic_widget);
+  } else {
+    tracker->RemovePictureInPictureWidget(glic_widget);
+  }
+}
+
+}  // namespace glic
diff --git a/chrome/browser/glic/glic_occlusion_notifier.h b/chrome/browser/glic/glic_occlusion_notifier.h
new file mode 100644
index 0000000..a0bc7c2
--- /dev/null
+++ b/chrome/browser/glic/glic_occlusion_notifier.h
@@ -0,0 +1,31 @@
+// Copyright 2025 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_GLIC_GLIC_OCCLUSION_NOTIFIER_H_
+#define CHROME_BROWSER_GLIC_GLIC_OCCLUSION_NOTIFIER_H_
+
+#include "chrome/browser/glic/widget/glic_window_controller.h"
+
+namespace glic {
+
+// The GlicOcclusionNotifier notifies the PictureInPictureOcclusionTracker when
+// to track the Glic window for occlusion of important security dialogs.
+class GlicOcclusionNotifier : public GlicWindowController::StateObserver {
+ public:
+  explicit GlicOcclusionNotifier(GlicWindowController& window_controller);
+  GlicOcclusionNotifier(const GlicOcclusionNotifier&) = delete;
+  GlicOcclusionNotifier& operator=(const GlicOcclusionNotifier&) = delete;
+  ~GlicOcclusionNotifier() override;
+
+  // GlicWindowController::StateObserver:
+  void PanelStateChanged(const mojom::PanelState& panel_state,
+                         Browser*) override;
+
+ private:
+  raw_ref<GlicWindowController> window_controller_;
+};
+
+}  // namespace glic
+
+#endif  // CHROME_BROWSER_GLIC_GLIC_OCCLUSION_NOTIFIER_H_
diff --git a/chrome/browser/glic/test_support/interactive_glic_test.h b/chrome/browser/glic/test_support/interactive_glic_test.h
index 1779f35..b84004a3 100644
--- a/chrome/browser/glic/test_support/interactive_glic_test.h
+++ b/chrome/browser/glic/test_support/interactive_glic_test.h
@@ -26,6 +26,8 @@
 #include "chrome/browser/glic/widget/glic_view.h"
 #include "chrome/browser/glic/widget/glic_widget.h"
 #include "chrome/browser/glic/widget/glic_window_controller.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker.h"
+#include "chrome/browser/picture_in_picture/picture_in_picture_window_manager.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_element_identifiers.h"
 #include "chrome/browser/ui/browser_list.h"
@@ -379,6 +381,17 @@
         expected_count, "CheckTabCount");
   }
 
+  auto CheckOcclusionTracked(bool expect_is_tracked) {
+    return Api::CheckResult(
+        [this]() {
+          return base::Contains(PictureInPictureWindowManager::GetInstance()
+                                    ->GetOcclusionTracker()
+                                    ->GetPictureInPictureWidgetsForTesting(),
+                                window_controller().GetGlicWidget());
+        },
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
index 12d0386..45d49e1 100644
--- a/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
+++ b/chrome/browser/glic/widget/glic_window_controller_interactive_uitest.cc
@@ -376,6 +376,12 @@
       WaitForState(test::internal::kGlicAppState, mojom::WebUiState::kReady));
 }
 
+IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest,
+                       DetachedWidgetIsTrackedByOcclusionTracker) {
+  RunTestSequence(OpenGlicWindow(GlicWindowMode::kDetached),
+                  CheckOcclusionTracked(true));
+}
+
 IN_PROC_BROWSER_TEST_F(GlicWindowControllerUiTest, TestInitialBounds) {
   // The GlicButton and Tabstrip are not actually shown until a tab is created.
   chrome::AddTabAt(browser(), GURL("about:blank"), 0, true);
@@ -680,6 +686,6 @@
                   InAnyContext(DetachGlicWindow(), MoveWidgetToSecondDisplay(),
                                CheckWidgetMovedToSecondaryDisplay(true)));
 }
-#endif
+#endif  // BUILDFLAG(IS_MAC)
 
 }  // namespace glic
diff --git a/chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker_unittest.cc b/chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker_unittest.cc
index b5590f8d..25a5a43 100644
--- a/chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker_unittest.cc
+++ b/chrome/browser/picture_in_picture/picture_in_picture_occlusion_tracker_unittest.cc
@@ -305,4 +305,41 @@
   observation.Observe(occludable_widget1.get());
 }
 
+TEST_F(PictureInPictureOcclusionTrackerTest,
+       CanStopObservingExistingPictureInPictureWidget) {
+  MockPictureInPictureOcclusionObserver observer;
+  ScopedPictureInPictureOcclusionObservation observation(&observer);
+  std::unique_ptr<views::Widget> picture_in_picture_widget =
+      CreatePictureInPictureWidget();
+
+  // Create a widget to track the occlusion state of, placing it so that it
+  // starts out occluded.
+  std::unique_ptr<views::Widget> occludable_widget =
+      CreateTestWidget(views::Widget::InitParams::CLIENT_OWNS_WIDGET);
+  occludable_widget->Show();
+  occludable_widget->SetBounds({50, 50, 200, 200});
+
+  // Start observing occlusion state. This should immediately tell the observer
+  // that it's occluded.
+  EXPECT_CALL(observer, OnOcclusionStateChanged(true));
+  observation.Observe(occludable_widget.get());
+  testing::Mock::VerifyAndClearExpectations(&observer);
+
+  // Tell the tracker to stop tracking the picture-in-picture widget. This
+  // should inform the observer that it's no longer occluded.
+  EXPECT_CALL(observer, OnOcclusionStateChanged(false));
+  PictureInPictureWindowManager::GetInstance()
+      ->GetOcclusionTracker()
+      ->RemovePictureInPictureWidget(picture_in_picture_widget.get());
+  testing::Mock::VerifyAndClearExpectations(&observer);
+
+  // Tell the tracker to start tracking the picture-in-picture widget again.
+  // This should inform the observer that it's occluded again.
+  EXPECT_CALL(observer, OnOcclusionStateChanged(true));
+  PictureInPictureWindowManager::GetInstance()
+      ->GetOcclusionTracker()
+      ->OnPictureInPictureWidgetOpened(picture_in_picture_widget.get());
+  testing::Mock::VerifyAndClearExpectations(&observer);
+}
+
 }  // namespace
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.