Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in WebAppInstalls
DescriptionMemory corruption in WebAppInstalls
ComponentWebAppInstalls
Sub-componentAndroid
Bug ClassMemory Corruption
Tracker562955271
Fix commitd5ca2e16e08a (chromium/src) +138/-18
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
  • chrome/android/junit/BUILD.gn
  • chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappFilterTouchUnitTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
From d5ca2e16e08aca9487cf5a077f87c29b2a73b06d Mon Sep 17 00:00:00 2001
From: Glenn Hartmann <hartmanng@chromium.org>
Date: Tue, 22 Sep 2026 06:51:14 -0700
Subject: [PATCH] [Android] Hoist anti-tapjacking touch guard to BaseCustomTabActivity

CustomTabActivity discards touch events that arrive while the activity is
not RESUMED on Android T-, guarding against taps routed through an overlay
activity owned by another app (crbug.com/40063907). The sibling activities
in the same BaseCustomTabActivity family that host installed PWAs/WebAPKs -
WebappActivity and SameTaskWebApkActivity - overrode no input-dispatch
method, and neither an ancestor nor a view-layer filter compensated, so
they accepted those events. These windows render without a URL bar and
typically host a long-lived logged-in session.

Move the guard from CustomTabActivity up into BaseCustomTabActivity so the
whole family is covered uniformly: CustomTabActivity,
TranslucentCustomTabActivity, WebappActivity, SameTaskWebApkActivity and
DevToolsActivity. CustomTabActivity retains only the CCT-specific,
flag-gated enter-animation block and delegates to super. The order of the
two checks is unchanged, so CCT behavior is unaffected.

On T+ the platform's ActivityRecordInputSink already drops these events,
which is why the guard stays gated on SDK_INT < TIRAMISU.
The enter-animation block is intentionally left CCT-only.

SameTaskWebApkActivity is launched with FLAG_ACTIVITY_NO_ANIMATION, so an
unconditional gate risks never unblocking, and a no-animation fallback
would disable it precisely where it would be needed. The remaining gap -
SameTaskWebApkActivity being translucent on SDK < S and accepting touches
from its first frame while RESUMED - needs a different signal and is not
addressed here.

TAG=agy
CONV=75134412-8ef9-4231-a294-f035431fec9b

Bug: 562955271
Test: run_chrome_junit_tests --gtest_filter=*WebappFilterTouchUnitTest*:*CustomTabsFilterTouchUnitTest*
Change-Id: Idec17a44937afdb772952a2bfcbaaa636a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8427844
Reviewed-by: Peter Conn <peconn@chromium.org>
Commit-Queue: Glenn Hartmann <hartmanng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1702568}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
index 2c2acee..256b749b 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/BaseCustomTabActivity.java
@@ -19,6 +19,7 @@
 import android.text.format.DateUtils;
 import android.util.Pair;
 import android.view.KeyEvent;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.Window;
@@ -30,9 +31,12 @@
 import androidx.annotation.ChecksSdkIntAtLeast;
 import androidx.annotation.IntDef;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.browser.customtabs.CustomTabsIntent;
 import androidx.browser.customtabs.TrustedWebUtils;
 
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
 import org.chromium.base.DeviceInfo;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.ResettersForTesting;
@@ -152,6 +156,16 @@
  * CustomTabActivity}.
  */
 public abstract class BaseCustomTabActivity extends ChromeActivity {
+    /**
+     * Prevents Tapjacking on T-. See crbug.com/40063907.
+     *
+     * <p>On Android T+ the platform's own {@code ActivityRecordInputSink} drops touches routed
+     * through an overlay owned by another app, so the activity-level guard is only needed below
+     * that.
+     */
+    private static final boolean sPreventTouches =
+            Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU;
+
     private static Integer sOverrideCoreCountForTesting;
 
     private final CipherFactory mCipherFactory = new CipherFactory();
@@ -1326,6 +1340,29 @@
         mBaseCustomTabRootUiCoordinator.onDeferredStartup();
     }
 
+    /**
+     * Discards touch events that arrive while this activity is not RESUMED, i.e. events which may
+     * be trickling down from an overlay activity above. See crbug.com/40063907.
+     *
+     * <p>This lives on {@link BaseCustomTabActivity} rather than on an individual subclass so that
+     * every activity in this family hosting web content is covered - notably the PWA/WebAPK windows
+     * ({@code WebappActivity}, {@code SameTaskWebApkActivity}), which render without a URL bar and
+     * typically host a logged-in session.
+     */
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (sPreventTouches && shouldPreventTouch()) {
+            return true;
+        }
+        return super.dispatchTouchEvent(ev);
+    }
+
+    @VisibleForTesting
+    public boolean shouldPreventTouch() {
+        if (ApplicationStatus.getStateForActivity(this) == ActivityState.RESUMED) return false;
+        return true;
+    }
+
     @Override
     public boolean dispatchKeyEvent(KeyEvent event) {
         Boolean result =
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
index 8d98298..4e77327 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/customtabs/CustomTabActivity.java
@@ -4,7 +4,6 @@
 
 package org.chromium.chrome.browser.customtabs;
 
-import static androidx.annotation.VisibleForTesting.PRIVATE;
 import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_DARK;
 import static androidx.browser.customtabs.CustomTabsIntent.COLOR_SCHEME_LIGHT;
 
@@ -28,11 +27,8 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
 import androidx.browser.customtabs.CustomTabsIntent;
 
-import org.chromium.base.ActivityState;
-import org.chromium.base.ApplicationStatus;
 import org.chromium.base.IntentUtils;
 import org.chromium.base.Log;
 import org.chromium.base.ResettersForTesting;
@@ -91,10 +87,6 @@
     private final CustomTabsConnection mConnection = CustomTabsConnection.getInstance();
     private int mNumOmniboxNavigationEventsPerSession;
 
-    /** Prevents Tapjacking on T-. See crbug.com/40063907 */
-    private static final boolean sPreventTouches =
-            Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU;
-
     private CustomTabsOpenTimeRecorder mOpenTimeRecorder;
 
     private static final boolean sBlockTouchesDuringEnterAnimation =
@@ -510,10 +502,8 @@
         if (sBlockTouchesDuringEnterAnimation && !mIsEnterAnimationCompleted) {
             return true;
         }
-        if (sPreventTouches && shouldPreventTouch()) {
-            // Discard the events which may be trickling down from an overlay activity above.
-            return true;
-        }
+        // The overlay/tapjacking guard lives in BaseCustomTabActivity so the whole family
+        // (including PWA/WebAPK windows) shares it. See crbug.com/40063907.
         return super.dispatchTouchEvent(ev);
     }
 
@@ -531,12 +521,6 @@
         super.finish();
     }
 
-    @VisibleForTesting(otherwise = PRIVATE)
-    boolean shouldPreventTouch() {
-        if (ApplicationStatus.getStateForActivity(this) == ActivityState.RESUMED) return false;
-        return true;
-    }
-
     /**
      * Show the web page with CustomTabActivity, without any navigation control. Used in showing the
      * terms of services page or help pages for Chrome.
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappFilterTouchUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappFilterTouchUnitTest.java
new file mode 100644
index 0000000..53557e60
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappFilterTouchUnitTest.java
@@ -0,0 +1,98 @@
+// Copyright 2026 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.webapps;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Build;
+import android.view.MotionEvent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import org.chromium.base.ActivityState;
+import org.chromium.base.ApplicationStatus;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.customtabs.BaseCustomTabActivity;
+
+/**
+ * Tests that the PWA/WebAPK windows filter touch events coming from an overlay activity, the same
+ * way {@link org.chromium.chrome.browser.customtabs.CustomTabActivity} does.
+ *
+ * <p>These activities host installed web apps with no URL bar, so an overlay-routed tap would
+ * otherwise reach an authenticated origin. See crbug.com/40063907 for the guard's origin.
+ */
+@RunWith(BaseRobolectricTestRunner.class)
+public class WebappFilterTouchUnitTest {
+    private static MotionEvent createTouchDown() {
+        return MotionEvent.obtain(
+                /* downTime= */ 0,
+                /* eventTime= */ 0,
+                MotionEvent.ACTION_DOWN,
+                /* x= */ 0f,
+                /* y= */ 0f,
+                /* metaState= */ 0);
+    }
+
+    private static void setUpTracking(BaseCustomTabActivity activity) {
+        // ApplicationStatus only starts tracking an activity on the CREATED transition.
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.CREATED);
+    }
+
+    private static void assertFiltersTouchesWhileNotResumed(BaseCustomTabActivity activity) {
+        setUpTracking(activity);
+
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.RESUMED);
+        assertFalse("Events should be accepted.", activity.shouldPreventTouch());
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.PAUSED);
+        assertTrue("Events should be discarded.", activity.shouldPreventTouch());
+
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.DESTROYED);
+    }
+
+    /**
+     * Asserts that a touch arriving while the activity is PAUSED is consumed rather than forwarded
+     * into the window, i.e. that {@code dispatchTouchEvent} is actually wired up to the guard.
+     */
+    private static void assertDiscardsTouchWhilePaused(BaseCustomTabActivity activity) {
+        setUpTracking(activity);
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.PAUSED);
+
+        MotionEvent event = createTouchDown();
+        try {
+            assertTrue(
+                    "Touch delivered while PAUSED should be discarded.",
+                    activity.dispatchTouchEvent(event));
+        } finally {
+            event.recycle();
+        }
+
+        ApplicationStatus.onStateChangeForTesting(activity, ActivityState.DESTROYED);
+    }
+
+    @Test
+    public void testWebappActivityShouldPreventTouch() {
+        assertFiltersTouchesWhileNotResumed(new WebappActivity());
+    }
+
+    @Test
+    public void testSameTaskWebApkActivityShouldPreventTouch() {
+        assertFiltersTouchesWhileNotResumed(new SameTaskWebApkActivity());
+    }
+
+    @Test
+    @Config(sdk = Build.VERSION_CODES.S_V2)
+    public void testWebappActivityDiscardsTouchWhilePaused_preT() {
+        assertDiscardsTouchWhilePaused(new WebappActivity());
+    }
+
+    @Test
+    @Config(sdk = Build.VERSION_CODES.S_V2)
+    public void testSameTaskWebApkActivityDiscardsTouchWhilePaused_preT() {
+        assertDiscardsTouchWhilePaused(new SameTaskWebApkActivity());
+    }
+}
diff --git a/chrome/android/junit/BUILD.gn b/chrome/android/junit/BUILD.gn
index ad9edbcb..6c85f34e 100644
--- a/chrome/android/junit/BUILD.gn
+++ b/chrome/android/junit/BUILD.gn
@@ -1601,6 +1601,7 @@
       "src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java",
       "src/org/chromium/chrome/browser/webapps/WebappDataStorageTest.java",
       "src/org/chromium/chrome/browser/webapps/WebappDirectoryManagerTest.java",
+      "src/org/chromium/chrome/browser/webapps/WebappFilterTouchUnitTest.java",
       "src/org/chromium/chrome/browser/webapps/WebappIntentDataProviderTest.java",
       "src/org/chromium/chrome/browser/webapps/WebappLauncherActivityTest.java",
       "src/org/chromium/chrome/browser/webapps/WebappRegistryTest.java",
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.