Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in FullScreen
DescriptionUI misrepresentation in FullScreen
ComponentFullScreen
Bug ClassLogic Error
Tracker503787232
Fix commit3e4d359b7a4d (chromium/src) +553/-116
CISA KEVNot listed
CreditedFrancesco Topol (k4tedu)
Disclosed2026-09-01

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java
modified
TEST_F
chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
modified
for
chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureInfobar.java
  • chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.java
  • chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java
  • chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.java
  • chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.cc
  • chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
From 3e4d359b7a4de442ecd9a5ecb2e8af37639d7568 Mon Sep 17 00:00:00 2001
From: Vikram Pasupathy <[email protected]>
Date: Thu, 11 Jun 2026 14:22:00 -0700
Subject: [PATCH] Android: Harden Snackbar system against security notice suppression

Implement structural improvements to Android's Snackbar and Exclusive
Access systems to prevent security notice bypasses and UI spoofing.

Design: http://shortn/_FiA9a66mBh
Bug: 517091927, 503787232, 514072194
Change-Id: Ie73529d96023e60fcaa9fb9ea28fafd73a8adae1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7896882
Reviewed-by: Jinsuk Kim <[email protected]>
Reviewed-by: Foromo Daniel Soromou <[email protected]>
Commit-Queue: Vikram Pasupathy <[email protected]>
Reviewed-by: Avi Drissman <[email protected]>
Reviewed-by: Muyao Xu <[email protected]>
Reviewed-by: Sirisha Kavuluru <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1645607}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureInfobar.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureInfobar.java
index d16bae42..d308fbca 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureInfobar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureInfobar.java
@@ -102,6 +102,7 @@
         String action = mResources.getString(R.string.ok);
         return Snackbar.make(title, mSnackbarController, type, code)
                 .setAction(action, null)
+                .setHighPriority(true)
                 .setDefaultLines(false);
     }
 
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.java b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.java
index 4e7f159..37cf26c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.java
@@ -61,6 +61,7 @@
 
         return Snackbar.make(title, controller, type, code)
                 .setAction(action, null)
+                .setHighPriority(true)
                 .setDuration(DURATION_MS)
                 .setDefaultLines(false);
     }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java b/chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java
index bc2646bf..5509167 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java
@@ -18,6 +18,8 @@
  */
 @NullMarked
 public class ExclusiveAccessBubble {
+    private static final int EXCLUSIVE_ACCESS_SNACKBAR_DURATION_MS = 3800;
+
     private final ExclusiveAccessContext mParentContext;
     private @Nullable Snackbar mSnackbar;
     private final SnackbarManager.SnackbarController mSnackbarController =
@@ -52,7 +54,10 @@
                                     Snackbar.UMA_EXCLUSIVE_ACCESS_BUBBLE)
                             // The exclusive access notice is security-critical and should not
                             // be discarded by the timeout of other action snackbars in the queue.
-                            .setHighPriority(true);
+                            .setHighPriority(true)
+                            // Use a Java-side timeout so that the timer only starts when
+                            // the notice is actually visible to the user.
+                            .setDuration(EXCLUSIVE_ACCESS_SNACKBAR_DURATION_MS);
             snackbarManager.showSnackbar(mSnackbar);
         }
     }
@@ -60,7 +65,23 @@
     @CalledByNative
     public void update(String text) {
         if (mText != null && mText.equals(text) && mSnackbar != null) return;
+        SnackbarManager snackbarManager = mParentContext.getSnackbarManager();
+        if (snackbarManager == null) return;
+
         mText = text;
+        if (mSnackbar != null) {
+            mSnackbar =
+                    Snackbar.make(
+                                    text,
+                                    mSnackbarController,
+                                    Snackbar.TYPE_ACTION,
+                                    Snackbar.UMA_EXCLUSIVE_ACCESS_BUBBLE)
+                            .setHighPriority(true)
+                            .setDuration(EXCLUSIVE_ACCESS_SNACKBAR_DURATION_MS);
+            // This will trigger SnackbarManager.updateView() and update the existing view.
+            snackbarManager.showSnackbar(mSnackbar);
+            return;
+        }
         hide();
         show();
     }
diff --git a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.java b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.java
index 05d29df..ca887f1 100644
--- a/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.java
+++ b/chrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.java
@@ -41,6 +41,7 @@
                                 Snackbar.TYPE_PERSISTENT,
                                 Snackbar.UMA_PRIVACY_SANDBOX_PAGE_OPEN)
                         .setAction(mContext.getString(R.string.more), null)
+                        .setHighPriority(true)
                         .setDefaultLines(false));
     }
 
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 3ffe446..dccce5e6 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
@@ -96,12 +96,14 @@
 }
 
 ExclusiveAccessBubbleAndroid::~ExclusiveAccessBubbleAndroid() {
-  Hide();
+  HideImmediately();
 }
 
 void ExclusiveAccessBubbleAndroid::Hide() {
-  was_shown_ = false;
-  bridge_->Hide();
+  // On Android, the exclusive access notice is managed by a Java-side timer
+  // that only starts once the snackbar is visible. We ignore the native
+  // timer dismissal here to prevent the notice from being hidden while
+  // it is covered by other UI elements.
 }
 
 void ExclusiveAccessBubbleAndroid::Show() {
@@ -110,7 +112,8 @@
 }
 
 void ExclusiveAccessBubbleAndroid::HideImmediately() {
-  Hide();
+  was_shown_ = false;
+  bridge_->Hide();
 }
 
 void ExclusiveAccessBubbleAndroid::Update(
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 c29f21816c..976f848 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
@@ -4,8 +4,13 @@
 
 #include "chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
 #include "base/android/jni_string.h"
 #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 "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -106,4 +111,45 @@
   EXPECT_CALL(*mock_bridge_ptr, Hide()).Times(1);
 }
 
+TEST_F(ExclusiveAccessBubbleAndroidTest, SnoozeResetForciblyReshowsNotice) {
+  ExclusiveAccessBubbleParams params;
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+
+  auto mock_bridge = std::make_unique<MockBridge>();
+  auto* mock_bridge_ptr = mock_bridge.get();
+
+  // Initial show on creation.
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(_)).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+
+  auto bubble = std::make_unique<ExclusiveAccessBubbleAndroid>(
+      params, base::DoNothing(), std::move(mock_bridge));
+
+  testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
+
+  ExclusiveAccessContextAndroid context;
+  context.SetBubbleForTesting(std::move(bubble));
+
+  // Verify that the first 9 user inputs don't trigger any show or update on the
+  // bridge.
+  for (int i = 1; i <= 9; ++i) {
+    context.OnExclusiveAccessUserInput();
+  }
+
+  // The 10th user input exceeds the snooze interaction threshold and must
+  // forcibly re-show the security notice regardless of whether it was
+  // previously shown in this session (i.e. force_update is set to true to
+  // override the was_shown_ latch).
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(_)).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+
+  context.OnExclusiveAccessUserInput();
+
+  testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
+}
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 c29f21816c..976f848 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
@@ -4,8 +4,13 @@
 
 #include "chrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.h"
 
+#include <memory>
+#include <string>
+#include <utility>
+
 #include "base/android/jni_string.h"
 #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 "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -106,4 +111,45 @@
   EXPECT_CALL(*mock_bridge_ptr, Hide()).Times(1);
 }
 
+TEST_F(ExclusiveAccessBubbleAndroidTest, SnoozeResetForciblyReshowsNotice) {
+  ExclusiveAccessBubbleParams params;
+  params.type = EXCLUSIVE_ACCESS_BUBBLE_TYPE_FULLSCREEN_EXIT_INSTRUCTION;
+
+  auto mock_bridge = std::make_unique<MockBridge>();
+  auto* mock_bridge_ptr = mock_bridge.get();
+
+  // Initial show on creation.
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(_)).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+
+  auto bubble = std::make_unique<ExclusiveAccessBubbleAndroid>(
+      params, base::DoNothing(), std::move(mock_bridge));
+
+  testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
+
+  ExclusiveAccessContextAndroid context;
+  context.SetBubbleForTesting(std::move(bubble));
+
+  // Verify that the first 9 user inputs don't trigger any show or update on the
+  // bridge.
+  for (int i = 1; i <= 9; ++i) {
+    context.OnExclusiveAccessUserInput();
+  }
+
+  // The 10th user input exceeds the snooze interaction threshold and must
+  // forcibly re-show the security notice regardless of whether it was
+  // previously shown in this session (i.e. force_update is set to true to
+  // override the was_shown_ latch).
+  EXPECT_CALL(*mock_bridge_ptr, IsVisible()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, IsKeyboardConnected()).WillOnce(Return(false));
+  EXPECT_CALL(*mock_bridge_ptr, Update(_)).Times(1);
+  EXPECT_CALL(*mock_bridge_ptr, Show()).Times(1);
+
+  context.OnExclusiveAccessUserInput();
+
+  testing::Mock::VerifyAndClearExpectations(mock_bridge_ptr);
+}
+
 }  // 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.