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 <[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.
References
On This Page