Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Messages
DescriptionInappropriate implementation in Messages
ComponentMessages
Bug ClassLogic Error
Tracker356658477
Fix commit95729cec0565 (chromium/src) +169/-1
CISA KEVNot listed
CreditedMohit Raj (shadow2639)
Disclosed2025-05-27

Files Changed

  • chrome/android/javatests/BUILD.gn
  • chrome/android/javatests/src/org/chromium/chrome/browser/messages/MessageTest.java
  • chrome/android/javatests/src/org/chromium/chrome/browser/messages/OWNERS
  • components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
From 95729cec0565596f159af487d0bceeb82a7755b8 Mon Sep 17 00:00:00 2001
From: Lijin Shen <[email protected]>
Date: Thu, 03 Apr 2025 15:02:33 -0700
Subject: [PATCH] Add tap protection to message ui

Disallow any touch event as soon as the animation starts. The message should become clickable again once the tap protection period ends.

Bug: 356658477
Change-Id: I3522cb07e0b9a4c6fbe9f88cbe12777ffc41b5d5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6411166
Reviewed-by: Aishwarya Rajesh <[email protected]>
Commit-Queue: Lijin Shen <[email protected]>
Reviewed-by: Theresa Sullivan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1442451}
---

diff --git a/chrome/android/javatests/BUILD.gn b/chrome/android/javatests/BUILD.gn
index 49c8aa56..efe5e69 100644
--- a/chrome/android/javatests/BUILD.gn
+++ b/chrome/android/javatests/BUILD.gn
@@ -1527,6 +1527,7 @@
     "src/org/chromium/chrome/browser/locale/LocaleManagerReferralTest.java",
     "src/org/chromium/chrome/browser/locale/LocaleManagerTest.java",
     "src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java",
+    "src/org/chromium/chrome/browser/messages/MessageTest.java",
     "src/org/chromium/chrome/browser/modaldialog/ChromeTabModalPresenterTest.java",
     "src/org/chromium/chrome/browser/modaldialog/ModalDialogViewRenderTest.java",
     "src/org/chromium/chrome/browser/multiwindow/MultiWindowIntegrationTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/messages/MessageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/messages/MessageTest.java
new file mode 100644
index 0000000..675a46a
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/messages/MessageTest.java
@@ -0,0 +1,105 @@
+// 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.
+
+package org.chromium.chrome.browser.messages;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.action.ViewActions.click;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.FakeTimeTestRule;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Batch;
+import org.chromium.base.test.util.CallbackHelper;
+import org.chromium.base.test.util.CommandLineFlags;
+import org.chromium.chrome.browser.ChromeTabbedActivity;
+import org.chromium.chrome.browser.flags.ChromeSwitches;
+import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
+import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
+import org.chromium.chrome.test.R;
+import org.chromium.components.messages.MessageBannerProperties;
+import org.chromium.components.messages.MessageDispatcher;
+import org.chromium.components.messages.MessageDispatcherProvider;
+import org.chromium.components.messages.MessagesTestHelper;
+import org.chromium.components.messages.PrimaryActionClickBehavior;
+import org.chromium.ui.modelutil.PropertyModel;
+
+import java.util.concurrent.TimeoutException;
+
+/** Integration test of Message. */
+@RunWith(ChromeJUnit4ClassRunner.class)
+@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
+@Batch(Batch.PER_CLASS)
+public class MessageTest {
+    @ClassRule
+    public static ChromeTabbedActivityTestRule sActivityTestRule =
+            new ChromeTabbedActivityTestRule();
+
+    @Rule public FakeTimeTestRule mFakeTimeTestRule = new FakeTimeTestRule();
+
+    private ChromeTabbedActivity mActivity;
+    private MessageDispatcher mMessageDispatcher;
+
+    @Before
+    public void setUp() {
+        sActivityTestRule.startMainActivityOnBlankPage();
+        mActivity = sActivityTestRule.getActivity();
+        mMessageDispatcher =
+                ThreadUtils.runOnUiThreadBlocking(
+                        () -> MessageDispatcherProvider.from(mActivity.getWindowAndroid()));
+    }
+
+    @After
+    public void tearDown() {
+        MessagesTestHelper.enableTapProtectionDuration(-1);
+    }
+
+    /**
+     * Test that message is not clickable within tap protection period and becomes clickable once it
+     * ends.
+     */
+    @Test
+    @SmallTest
+    public void testTapProtection() throws TimeoutException {
+        MessagesTestHelper.enableTapProtectionDuration(500);
+        CallbackHelper helper = new CallbackHelper();
+        PropertyModel model =
+                ThreadUtils.runOnUiThreadBlocking(
+                        () ->
+                                new PropertyModel.Builder(MessageBannerProperties.ALL_KEYS)
+                                        .with(MessageBannerProperties.TITLE, "Test title")
+                                        .with(MessageBannerProperties.PRIMARY_BUTTON_TEXT, "Action")
+                                        .with(MessageBannerProperties.ON_DISMISSED, (v) -> {})
+                                        .with(
+                                                MessageBannerProperties.ON_PRIMARY_ACTION,
+                                                () -> {
+                                                    helper.notifyCalled();
+                                                    return PrimaryActionClickBehavior
+                                                            .DISMISS_IMMEDIATELY;
+                                                })
+                                        .build());
+        ThreadUtils.runOnUiThreadBlocking(
+                () -> {
+                    mMessageDispatcher.enqueueWindowScopedMessage(model, true);
+                });
+        onView(withId(R.id.message_primary_button)).check(matches(isDisplayed()));
+        onView(withId(R.id.message_primary_button)).perform(click());
+        Assert.assertEquals("Not clickable with tap protection period", 0, helper.getCallCount());
+        mFakeTimeTestRule.advanceMillis(1500);
+        onView(withId(R.id.message_primary_button)).perform(click());
+        helper.waitForNext("Should able to click when tap protection period ends");
+    }
+}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/messages/OWNERS b/chrome/android/javatests/src/org/chromium/chrome/browser/messages/OWNERS
new file mode 100644
index 0000000..12c1e52
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/messages/OWNERS
@@ -0,0 +1 @@
+file://components/messages/OWNERS
\ No newline at end of file
diff --git a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
index 7d04e76a..dd43d31 100644
--- a/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
+++ b/components/messages/android/internal/java/src/org/chromium/components/messages/MessageBannerMediator.java
@@ -7,6 +7,7 @@
 import static org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection.DOWN;
 import static org.chromium.components.browser_ui.widget.gesture.SwipeGestureListener.ScrollDirection.UP;
 import static org.chromium.components.messages.MessageBannerProperties.CONTENT_ALPHA;
+import static org.chromium.components.messages.MessageBannerProperties.IS_WITHIN_TAP_PROTECTION_PERIOD_SUPPLIER;
 import static org.chromium.components.messages.MessageBannerProperties.MARGIN_TOP;
 import static org.chromium.components.messages.MessageBannerProperties.TRANSLATION_X;
 import static org.chromium.components.messages.MessageBannerProperties.TRANSLATION_Y;
@@ -21,7 +22,10 @@
 import androidx.annotation.IntDef;
 
 import org.chromium.base.MathUtils;
+import org.chromium.base.ResettersForTesting;
+import org.chromium.base.TimeUtils;
 import org.chromium.base.supplier.Supplier;
+import org.chromium.build.BuildConfig;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.components.browser_ui.widget.animation.CancelAwareAnimatorListener;
@@ -55,6 +59,8 @@
         int GESTURE = 3;
     }
 
+    private static final long TAP_PROTECTION_DURATION_MS = 500;
+
     private static final int ENTER_DURATION_MS = 550;
     private static final int EXIT_DURATION_MS = 350;
     private static final TimeInterpolator TRANSLATION_ENTER_INTERPOLATOR =
@@ -63,6 +69,8 @@
             Interpolators.EMPHASIZED_DECELERATE;
     private static final TimeInterpolator EXIT_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
 
+    private static long sTapProtectionDurationMsForTesting;
+
     private final PropertyModel mModel;
     private final Supplier<Integer> mMaxTranslationYSupplier;
     private final Supplier<Integer> mTopOffsetSupplier;
@@ -132,6 +140,20 @@
             mModel.set(TRANSLATION_Y, mModel.get(MARGIN_TOP) - mDefaultMarginTop);
             mModel.set(MARGIN_TOP, mDefaultMarginTop);
         }
+        if (toIndex == Position.FRONT
+                && (!BuildConfig.IS_FOR_TEST || sTapProtectionDurationMsForTesting > 0)) {
+            long startTimestamp = TimeUtils.elapsedRealtimeMillis();
+            long protectionDuration =
+                    sTapProtectionDurationMsForTesting > 0
+                            ? sTapProtectionDurationMsForTesting
+                            : TAP_PROTECTION_DURATION_MS;
+            mModel.set(
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/messages/android/test/java/src/org/chromium/components/messages/MessagesTestHelper.java b/components/messages/android/test/java/src/org/chromium/components/messages/MessagesTestHelper.java
index c1bbb27cb..66a5e45 100644
--- a/components/messages/android/test/java/src/org/chromium/components/messages/MessagesTestHelper.java
+++ b/components/messages/android/test/java/src/org/chromium/components/messages/MessagesTestHelper.java
@@ -150,6 +150,14 @@
         return ((SingleActionMessage) messageStateHandler).getModelForTesting();
     }
 
+    /**
+     * Enable tap protection duration in Test environment, which is disabled by default in Test
+     * environment.
+     */
+    public static void enableTapProtectionDuration(long duration) {
+        MessageBannerMediator.setTapProtectionDurationMsForTesting(duration);
+    }
+
     @NativeMethods
     interface Natives {
         void onMessageEnqueued(long nativeMessagesTestHelper);
Loading diff…

Original Bug Report

reported by [email protected]

Android Chrome External Navigation Bubble Tapjacking

Steps to reproduce the problem

  1. Make sure “com.sec.android.app.sbrowser” is installed on your Android device. (https://play.google.com/store/apps/details?id=com.sec.android.app.sbrowser)
  2. Open Chrome and visit - http://bugtest.unaux.com/bubble.html
  3. Click on the red box 3 times
  4. Notice, that “Samsung Browser” is triggered accidentally and inside it “http://evil.com” is opened.

Problem Description

Previously I reported issue 356038470 “Tapjacking on Intent chooser dialog box” which got marked as duplicate, so I got another way to perform browser downgrade attack using the External Navigation bubble tapjacking, which prompts user’s to open any installed apps using INTENT redirects.

If an attacker tricks user’s into clicking 2-3 times on the webpage on a specific point then the user’s will accidentally trigger the INTENT redirect and end up opening any malicious INTENTS.

Summary

Android Chrome External Navigation Bubble Tapjacking

Custom Questions

Reporter credit:

Mohit Raj (shadow2639)

Additional Data

Category: Security
Chrome Channel: Stable
Regression: N/A

View on issue tracker