Chrome · FullScreen
CVE-2026-84356
Logic Error in FullScreen
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.java |
modified | |
TEST_Fchrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc |
modified | |
forchrome/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.javachrome/android/java/src/org/chromium/chrome/browser/browserservices/ui/view/DisclosureSnackbar.javachrome/android/java/src/org/chromium/chrome/browser/ui/ExclusiveAccessBubble.javachrome/browser/privacy_sandbox/android/java/src/org/chromium/chrome/browser/privacy_sandbox/PrivacySandboxSnackbarController.javachrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android.ccchrome/browser/ui/android/exclusive_access/exclusive_access_bubble_android_unittest.cc
Patch
From 3e4d359b7a4de442ecd9a5ecb2e8af37639d7568 Mon Sep 17 00:00:00 2001
From: Vikram Pasupathy <vpasupathy@chromium.org>
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 <jinsukkim@chromium.org>
Reviewed-by: Foromo Daniel Soromou <koretadaniel@chromium.org>
Commit-Queue: Vikram Pasupathy <vpasupathy@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Reviewed-by: Muyao Xu <muyaoxu@google.com>
Reviewed-by: Sirisha Kavuluru <skavuluru@google.com>
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.
References
On This Page