Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in FullScreen
DescriptionMemory corruption in FullScreen
ComponentFullScreen
Sub-componentChrome
Bug ClassMemory Corruption
Tracker514008724
Fix commitdc374dc36249 (chromium/src) +76/-14
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
modified
if
chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc
modified

Files Changed

  • chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
  • chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc
  • chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
From dc374dc36249b531b9a5d3b59e8574519781c0cf Mon Sep 17 00:00:00 2001
From: Vikram Pasupathy <[email protected]>
Date: Mon, 21 Sep 2026 12:52:43 -0700
Subject: [PATCH] chrome: Unlatch download override notification on fullscreen exit update

When a download begins while in fullscreen, notify_overridden_ was
previously sticky and kept params_.has_download pinned to true for all
subsequent updates and re-shows. Because GetInstructionTextForType()
omits the origin whenever has_download is true, the site origin was
permanently suppressed after any download occurred during fullscreen.

This CL unlatches notify_overridden_ when !params.has_download or when
the origin changes, ensuring the origin is restored once the download
notification clears.

Bug: 514008724
Change-Id: I9967c259dba339095464a7d6ed1e2ab2fd12b9a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8429630
Reviewed-by: Foromo Daniel Soromou <[email protected]>
Commit-Queue: Vikram Pasupathy <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1701980}
---
diff --git a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
index dccce5e6..322a4b5 100644
--- a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
+++ b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
@@ -123,7 +123,8 @@
          params.has_download);
   bool already_shown = IsVisible();
   if (params_.type == params.type && params_.origin == params.origin &&
-      !params.force_update && (already_shown || was_shown_)) {
+      params_.has_download == params.has_download && !params.force_update &&
+      (already_shown || was_shown_)) {
     return;
   }
   was_shown_ = true;
@@ -137,9 +138,13 @@
   //    or the previous notification was about an override itself.
   // If both the previous and current notifications have a download, but
   // neither is an override, then we don't need to show an override.
-  notify_overridden_ =
-      already_shown &&
-      (notify_overridden_ || (params.has_download ^ params_.has_download));
+  if (!params.has_download && !params.origin.opaque()) {
+    notify_overridden_ = false;
+  } else {
+    notify_overridden_ =
+        already_shown &&
+        (notify_overridden_ || (params.has_download ^ params_.has_download));
+  }
   params_.has_download = params.has_download || notify_overridden_;
 
   // Bubble maybe be reused after timeout.
@@ -147,10 +152,10 @@
 
   first_hide_callback_ = std::move(first_hide_callback);
 
-  params_.origin = params.origin;
-  // When a request to notify about a download is made, the bubble type
-  // should be preserved from the old value, and not be updated.
+  // When a request to notify about a download is made, the bubble type and
+  // origin should be preserved from the old value, and not be updated.
   if (!params.has_download) {
+    params_.origin = params.origin;
     params_.type = params.type;
   }
   UpdateBubbleContent(params_.type);
diff --git a/chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc b/chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc
index f4f21de..3df2459 100644
--- a/chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc
+++ b/chrome/browser/ui/views/exclusive_access/exclusive_access_bubble_views.cc
@@ -199,7 +199,8 @@
          params.has_download);
   bool already_shown = IsShowing() || IsVisible();
   if (params_.type == params.type && params_.origin == params.origin &&
-      !params.force_update && already_shown) {
+      params_.has_download == params.has_download && !params.force_update &&
+      already_shown) {
     return;
   }
 
@@ -209,9 +210,13 @@
   //    or the previous notification was about an override itself.
   // If both the previous and current notifications have a download, but
   // neither is an override, then we don't need to show an override.
-  notify_overridden_ =
-      already_shown &&
-      (notify_overridden_ || (params.has_download ^ params_.has_download));
+  if (!params.has_download && !params.origin.opaque()) {
+    notify_overridden_ = false;
+  } else {
+    notify_overridden_ =
+        already_shown &&
+        (notify_overridden_ || (params.has_download ^ params_.has_download));
+  }
   params_.has_download = params.has_download || notify_overridden_;
 
   // Bubble maybe be re-used after timeout.
@@ -222,10 +227,10 @@
   const bool entering_tab_fullscreen =
       !IsTabFullscreenType(params_.type) && IsTabFullscreenType(params.type);
 
-  params_.origin = params.origin;
-  // When a request to notify about a download is made, the bubble type
-  // should be preserved from the old value, and not be updated.
+  // When a request to notify about a download is made, the bubble type and
+  // origin should be preserved from the old value, and not be updated.
   if (!params.has_download) {
+    params_.origin = params.origin;
     params_.type = params.type;
   }
   UpdateViewContent(params_.type);
diff --git a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
index 2be564f..330f55c 100644
--- a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
+++ b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/functional/callback_helpers.h"
 #include "chrome/browser/ui/android/exclusive_access/exclusive_access_context_android.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/url_formatter/elide_url.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -260,4 +261,55 @@
   testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
 }
 
+TEST_F(ExclusiveAccessBubbleAndroidTest,
+       DownloadCompletionRestoresOriginInNotice) {
+  url::Origin origin = url::Origin::Create(GURL("https://example.com"));
+  auto format_text = [&](bool has_download) {
+    return exclusive_access_bubble::GetInstructionTextForTypeTouchBased(
+        EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
+        url_formatter::FormatOriginForSecurityDisplay(
+            origin, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC),
+        has_download, /*notify_overridden=*/has_download);
+  };
+
+  ExclusiveAccessBubbleParams params;
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+  params.origin = origin;
+
+  auto mock_bridge = std::make_unique<MockBridge>();
+  auto* mock_bridge_ptr = mock_bridge.get();
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected())
+      .WillRepeatedly(Return(false));
+
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(false))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  ExclusiveAccessBubbleAndroid bubble(params, base::DoNothing(),
+                                      std::move(mock_bridge));
+
+  // Download starts while in fullscreen (DownloadDisplayController passes an
+  // empty/opaque origin), overriding the notice.
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE;
+  params.origin = url::Origin();
+  params.has_download = true;
+  params.force_update = true;
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(true))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  bubble.Update(params, base::DoNothing());
+  EXPECT_TRUE(bubble.params().has_download);
+
+  // Subsequent update with a non-opaque origin unlatches override and restores
+  // the origin in the notice.
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+  params.origin = origin;
+  params.has_download = false;
+  params.force_update = false;
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(false))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  bubble.Update(params, base::DoNothing());
+  EXPECT_FALSE(bubble.params().has_download);
+}
+
 }  // namespace
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
index 2be564f..330f55c 100644
--- a/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
+++ b/chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
@@ -12,6 +12,7 @@
 #include "base/functional/callback_helpers.h"
 #include "chrome/browser/ui/android/exclusive_access/exclusive_access_context_android.h"
 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
+#include "components/url_formatter/elide_url.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "ui/base/l10n/l10n_util.h"
@@ -260,4 +261,55 @@
   testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
 }
 
+TEST_F(ExclusiveAccessBubbleAndroidTest,
+       DownloadCompletionRestoresOriginInNotice) {
+  url::Origin origin = url::Origin::Create(GURL("https://example.com"));
+  auto format_text = [&](bool has_download) {
+    return exclusive_access_bubble::GetInstructionTextForTypeTouchBased(
+        EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION,
+        url_formatter::FormatOriginForSecurityDisplay(
+            origin, url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC),
+        has_download, /*notify_overridden=*/has_download);
+  };
+
+  ExclusiveAccessBubbleParams params;
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+  params.origin = origin;
+
+  auto mock_bridge = std::make_unique<MockBridge>();
+  auto* mock_bridge_ptr = mock_bridge.get();
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected())
+      .WillRepeatedly(Return(false));
+
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(false))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  ExclusiveAccessBubbleAndroid bubble(params, base::DoNothing(),
+                                      std::move(mock_bridge));
+
+  // Download starts while in fullscreen (DownloadDisplayController passes an
+  // empty/opaque origin), overriding the notice.
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE;
+  params.origin = url::Origin();
+  params.has_download = true;
+  params.force_update = true;
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(true))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  bubble.Update(params, base::DoNothing());
+  EXPECT_TRUE(bubble.params().has_download);
+
+  // Subsequent update with a non-opaque origin unlatches override and restores
+  // the origin in the notice.
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+  params.origin = origin;
+  params.has_download = false;
+  params.force_update = false;
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(true));
+  EXPECT_CALL(*mock_bridge_ptr, Update(format_text(false))).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+  bubble.Update(params, base::DoNothing());
+  EXPECT_FALSE(bubble.params().has_download);
+}
+
 }  // 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.