CVE-2025-13102
Overview
Background
- Bottom sheet
- The Android UI surface implemented by
BottomSheet.javathat slides up from the bottom of the screen to host content such as the WebApp (PWA) install prompt. - `SheetState.SCROLLING`
- The transient
SheetStateenum value indicating the sheet is mid-animation between resting positions (e.g. settling open or closed). - `TouchRestrictingFrameLayout`
- The removed custom
FrameLayoutsubclass whose stated job was to “ignore all user input based on the state of the bottom sheet,” delegating the gating decision to theBottomSheetit held viasetBottomSheet. - Touch event dispatch
- Android routes pointer input through
onInterceptTouchEvent(a parent’s chance to claim the gesture) and thenonTouchEvent(where the claiming view consumes it), so a view must decide there whether input is allowed.
Root Cause Analysis
While the bottom sheet was animating between states, its position on screen was changing under the user’s finger, yet the input pipeline still forwarded touches to mGestureDetector and, through it, to whatever content the sheet was displaying. The invariant that “a control should not accept an activating tap while it is sliding into a new position” was not enforced in BottomSheet.onInterceptTouchEvent or BottomSheet.onTouchEvent; the pre-patch gating logic in TouchRestrictingFrameLayout only suppressed input in coarse cases and did not cover the SheetState.SCROLLING animation window.
As a result, a tap aimed at one part of the UI could land on a different, unexpected target (such as an install-confirmation affordance) once the animation shifted the layout — a classic tapjacking / input-timing race.
The fix adds an explicit early return in both onInterceptTouchEvent and onTouchEvent that swallows all touches whenever getSheetState() equals SheetState.SCROLLING, so no gesture reaches the detector or content while the sheet is in motion. Because the gating now lives directly in BottomSheet at the true dispatch boundary, the special-purpose TouchRestrictingFrameLayout is deleted and its holders are replaced with plain FrameLayout instances.
SheetState.SCROLLING) window unguarded, allowing taps to be consumed against a surface whose position was still moving; the fix closes the window by returning early from both touch entry points whenever the sheet is scrolling.Attack Path
- Trigger the sheet
A malicious or misleading page/flow causes the WebApp install bottom sheet (or another bottom-sheet UI) to begin animating open or settling into a new
SheetState. - Enter the animation window
During the transition the sheet reports
SheetState.SCROLLING, and its content moves under the pointer while touch dispatch remains active. - Land a mistimed tap
The user taps where they believe a benign element sits, but the shifting layout places an actionable control (e.g. an install/confirm button) under the finger at the moment
onTouchEventconsumes it. - Confirm an unintended action The gesture is delivered to the now-repositioned target, completing an action the user did not intend, such as accepting a WebApp install prompt.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
TouchRestrictingFrameLayoutcomponents/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java |
modified |
Files Changed
chrome/android/expectations/lint-baseline.xmlcomponents/browser_ui/bottomsheet/android/internal/BUILD.gncomponents/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.javacomponents/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.javacomponents/browser_ui/bottomsheet/android/java/res/layout/bottom_sheet.xml
Audit Directions
- Animation-window input gatingAudit other views and sheets that reposition via animators for touch handlers that consume input without checking whether the view is mid-transition (e.g. missing a
SheetState.SCROLLING-equivalent guard). - Split gating responsibilityLook for input suppression logic that lives in a wrapper/
FrameLayoutrather than at the trueonInterceptTouchEvent/onTouchEventdispatch boundary, since indirection tends to leave states (like animation) unhandled. - State-machine coverageReview touch/input decisions that enumerate only some values of a UI state enum, checking that every transient state (settling, hiding, scrolling) is explicitly accounted for before input is accepted.
Patch
From 83df5710daca3a1fbdde22c0b9e5fde6cf8db81c Mon Sep 17 00:00:00 2001
From: Lijin Shen <lazzzis@google.com>
Date: Thu, 19 Dec 2024 13:16:42 -0800
Subject: [PATCH] [clank-q4-fixit] Disable touch when bottom sheet is animating
Bug: 351564774
Change-Id: I7f7b94f3b82945361bff8703d3e6fc5aa2cf9234
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6102411
Reviewed-by: Matthew Jones <mdjones@chromium.org>
Commit-Queue: Lijin Shen <lazzzis@google.com>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/heads/main@{#1398821}
---
diff --git a/chrome/android/expectations/lint-baseline.xml b/chrome/android/expectations/lint-baseline.xml
index a71f1c6..4c67eb1 100644
--- a/chrome/android/expectations/lint-baseline.xml
+++ b/chrome/android/expectations/lint-baseline.xml
@@ -2499,17 +2499,6 @@
</issue>
<issue
- id="ClickableViewAccessibility"
- message="Custom view `TouchRestrictingFrameLayout` overrides `onTouchEvent` but not `performClick`"
- errorLine1=" public boolean onTouchEvent(MotionEvent event) {"
- errorLine2=" ~~~~~~~~~~~~">
- <location
- file="../../components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java"
- line="42"
- column="20"/>
- </issue>
-
- <issue
id="AccessibilityWindowStateChangedEvent"
message="Manually populating or sending TYPE_WINDOW_STATE_CHANGED events should be avoided. They may be ignored on certain versions of Android. Prefer setting UI metadata using `View.onInitializeAccessibilityNodeInfo`, `Activity.setTitle`, `ViewCompat.setAccessibilityPaneTitle`, etc. to inform users of crucial changes to the UI."
errorLine1=" mListView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);"
diff --git a/components/browser_ui/bottomsheet/android/internal/BUILD.gn b/components/browser_ui/bottomsheet/android/internal/BUILD.gn
index 0b68022..35122f5 100644
--- a/components/browser_ui/bottomsheet/android/internal/BUILD.gn
+++ b/components/browser_ui/bottomsheet/android/internal/BUILD.gn
@@ -16,7 +16,6 @@
"java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerFactory.java",
"java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetControllerImpl.java",
"java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheetSwipeDetector.java",
- "java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java",
]
deps = [
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
index 1c02a9a..00f5ee7 100644
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
+++ b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/BottomSheet.java
@@ -130,7 +130,7 @@
@Nullable protected BottomSheetContent mSheetContent;
/** A handle to the FrameLayout that holds the content of the bottom sheet. */
- private TouchRestrictingFrameLayout mBottomSheetContentContainer;
+ private FrameLayout mBottomSheetContentContainer;
/**
* The last offset ratio sent to observers of onSheetOffsetChanged(). This is used to ensure the
@@ -139,7 +139,7 @@
private float mLastOffsetRatioSent;
/** The FrameLayout used to hold the bottom sheet toolbar. */
- private TouchRestrictingFrameLayout mToolbarHolder;
+ private FrameLayout mToolbarHolder;
/** Whether the {@link BottomSheet} and its children should react to touch events. */
private boolean mIsTouchEnabled;
@@ -247,7 +247,9 @@
mSettleAnimator = null;
}
- /** @return Whether the sheet is in the process of hiding. */
+ /**
+ * @return Whether the sheet is in the process of hiding.
+ */
boolean isHiding() {
return mSettleAnimator != null && mTargetState == SheetState.HIDDEN;
}
@@ -264,6 +266,9 @@
if (isHiding()) return false;
+ // No interaction when sheet is animating.
+ if (getSheetState() == SheetState.SCROLLING) return true;
+
return mGestureDetector.onInterceptTouchEvent(e);
}
@@ -277,6 +282,9 @@
// anything with them.
if (!mIsTouchEnabled) return true;
+ // No interaction when sheet is animating.
+ if (getSheetState() == SheetState.SCROLLING) return true;
+
mGestureDetector.onTouchEvent(e);
return true;
@@ -314,12 +322,9 @@
onAppHeaderHeightChanged(appHeaderHeight);
setBottomMargin(bottomMargin);
- mToolbarHolder =
- (TouchRestrictingFrameLayout) findViewById(R.id.bottom_sheet_toolbar_container);
+ mToolbarHolder = (FrameLayout) findViewById(R.id.bottom_sheet_toolbar_container);
- mBottomSheetContentContainer =
- (TouchRestrictingFrameLayout) findViewById(R.id.bottom_sheet_content);
- mBottomSheetContentContainer.setBottomSheet(this);
+ mBottomSheetContentContainer = (FrameLayout) findViewById(R.id.bottom_sheet_content);
mContainerWidth = mSheetContainer.getWidth();
mContainerHeight = mSheetContainer.getHeight();
diff --git a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java b/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java
deleted file mode 100644
index bf114fe9..0000000
--- a/components/browser_ui/bottomsheet/android/internal/java/src/org/chromium/components/browser_ui/bottomsheet/TouchRestrictingFrameLayout.java
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2018 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.components.browser_ui.bottomsheet;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.MotionEvent;
-import android.widget.FrameLayout;
-
-/**
- * A specialized FrameLayout that is capable of ignoring all user input based on the state of
- * the bottom sheet.
- */
-class TouchRestrictingFrameLayout extends FrameLayout {
- /** A handle to the bottom sheet. */
- private BottomSheet mBottomSheet;
-
- public TouchRestrictingFrameLayout(Context context, AttributeSet atts) {
- super(context, atts);
- }
-
- /** @param sheet The bottom sheet. */
- public void setBottomSheet(BottomSheet sheet) {
- mBottomSheet = sheet;
- }
-
- /** @return Whether touch is enabled. */
- private boolean isTouchDisabled() {
- return mBottomSheet == null
- || mBottomSheet.getSheetState() == BottomSheetController.SheetState.SCROLLING;
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (isTouchDisabled()) return false;
- return super.onInterceptTouchEvent(event);
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (isTouchDisabled()) return false;
- return super.onTouchEvent(event);
- }
-}
diff --git a/components/browser_ui/bottomsheet/android/java/res/layout/bottom_sheet.xml b/components/browser_ui/bottomsheet/android/java/res/layout/bottom_sheet.xml
index b10b6ce4..04ce93e 100644
--- a/components/browser_ui/bottomsheet/android/java/res/layout/bottom_sheet.xml
+++ b/components/browser_ui/bottomsheet/android/java/res/layout/bottom_sheet.xml
@@ -6,7 +6,6 @@
-->
<org.chromium.components.browser_ui.bottomsheet.BottomSheet
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/bottom_sheet"
android:layout_width="wrap_content"
android:layout_height="match_parent"
@@ -25,7 +24,7 @@
android:layout_height="match_parent"
android:background="@drawable/bottom_sheet_background" />
- <org.chromium.components.browser_ui.bottomsheet.TouchRestrictingFrameLayout
+ <FrameLayout
android:importantForAccessibility="yes"
android:id="@+id/bottom_sheet_content"
android:layout_width="match_parent"
@@ -38,8 +37,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" >
- <view
- class="org.chromium.components.browser_ui.bottomsheet.TouchRestrictingFrameLayout"
+ <FrameLayout
android:id="@+id/bottom_sheet_toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Original Bug Report
The PWA's installation dialog isn't being dismissed after redirects, which allows an attacker to sho
Steps to reproduce the problem
go to mrnoob790.github.io/index.html click on add homescreen and then fastly click on install app u will installation popup come in screen and website is redirect to google.com
Problem Description
After the PWA’s installation dialog is opened by the user, it is possible to redirect the attacker’s page to another website, and given the dialog isn’t being dismissed, it will show over cross-origin websites
Summary
The PWA’s installation dialog isn’t being dismissed after redirects, which allows an attacker to sho
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A