CVE-2026-14114
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java |
modified |
Files Changed
components/webapps/browser/android/BUILD.gncomponents/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.javacomponents/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
Patch
From bd577c2f32d371594b0d234b157c4b4911e98eb1 Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Wed, 20 May 2026 13:58:15 -0700
Subject: [PATCH] [webapps] Restrict shortcut fallback to default launcher
Chrome for Android fell back to sending an implicit broadcast with
action com.android.launcher.action.INSTALL_SHORTCUT when ShortcutManager
was not supported. This implicit broadcast could be intercepted by
malicious apps, leaking the WebApp launch MAC (sensitive HMAC-SHA256)
and URL.
Instead of removing the fallback entirely (which broke add to homescreen
functionality on some devices/distributions), this CL re-implements the
fallback by explicitly targeting the default launcher package.
We query the package manager for the default home activity, and if found
(and it's not the system resolver), we set it as the target package for
the INSTALL_SHORTCUT broadcast.
We also update isAddToHomeIntentSupported to verify if the default
launcher actually has a receiver for this broadcast.
TAG=agy
CONV=baf5d000-8241-49f3-90fd-21c834bccdaa
Bug: 513743129, b:514440631
Change-Id: I76d5df87f2d1e00d8d9ed7cf31dde1a4bd6053e3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7858490
Reviewed-by: Glenn Hartmann <hartmanng@chromium.org>
Auto-Submit: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633816}
---
diff --git a/components/webapps/browser/android/BUILD.gn b/components/webapps/browser/android/BUILD.gn
index 64dfdb9d..1ef7676 100644
--- a/components/webapps/browser/android/BUILD.gn
+++ b/components/webapps/browser/android/BUILD.gn
@@ -205,6 +205,7 @@
"java/src/org/chromium/components/webapps/AddToHomescreenDialogViewTest.java",
"java/src/org/chromium/components/webapps/AddToHomescreenMediatorTest.java",
"java/src/org/chromium/components/webapps/AddToHomescreenViewBinderTest.java",
+ "java/src/org/chromium/components/webapps/WebappsUtilsTest.java",
"java/src/org/chromium/components/webapps/pwa_restore_ui/PwaRestoreBottomSheetContentTest.java",
"java/src/org/chromium/components/webapps/pwa_restore_ui/PwaRestoreBottomSheetCoordinatorTest.java",
"java/src/org/chromium/components/webapps/pwa_universal_install/PwaUniversalInstallBottomSheetContentTest.java",
@@ -227,6 +228,7 @@
"//content/public/android:content_java",
"//content/public/test/android:content_java_test_support",
"//third_party/androidx:androidx_core_core_java",
+ "//third_party/androidx:androidx_test_core_java",
"//third_party/androidx:androidx_test_runner_java",
"//third_party/jni_zero:jni_zero_java",
"//third_party/junit",
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
index af7e1172..d7a64961 100644
--- a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
@@ -4,7 +4,6 @@
package org.chromium.components.webapps;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -84,7 +83,13 @@
return;
}
+ String defaultLauncher = getDefaultLauncherPackageName();
+ if (defaultLauncher == null) {
+ Log.w(TAG, "ShortcutManager is not supported and no default launcher found to target.");
+ return;
+ }
Intent intent = createAddToHomeIntent(title, icon, shortcutIntent);
+ intent.setPackage(defaultLauncher);
ContextUtils.getApplicationContext().sendBroadcast(intent);
showAddedToHomescreenToast(title);
}
@@ -155,16 +160,37 @@
*
* @return if a shortcut can be added to the home screen under the current profile.
*/
- @SuppressLint("WrongConstant")
public static boolean isAddToHomeIntentSupported() {
if (isRequestPinShortcutSupported()) return true;
+
+ String defaultLauncher = getDefaultLauncherPackageName();
+ if (defaultLauncher == null) return false;
+
PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
Intent i = new Intent(INSTALL_SHORTCUT);
- List<ResolveInfo> receivers =
- pm.queryBroadcastReceivers(i, PackageManager.GET_INTENT_FILTERS);
+ i.setPackage(defaultLauncher);
+ List<ResolveInfo> receivers = pm.queryBroadcastReceivers(i, 0);
return !receivers.isEmpty();
}
+ private static @Nullable String getDefaultLauncherPackageName() {
+ Intent intent = new Intent(Intent.ACTION_MAIN);
+ intent.addCategory(Intent.CATEGORY_HOME);
+ PackageManager pm = ContextUtils.getApplicationContext().getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY);
+ if (resolveInfo == null || resolveInfo.activityInfo == null) {
+ return null;
+ }
+ String packageName = resolveInfo.activityInfo.packageName;
+ // If the resolveInfo is the system resolver (e.g., if there are multiple launchers
+ // and the user hasn't selected a default), we treat it as no default launcher.
+ if ("android".equals(packageName)
+ || "com.android.internal.app.ResolverActivity".equals(packageName)) {
+ return null;
+ }
+ return packageName;
+ }
+
/** Prepares whether Android O's ShortcutManager.requestPinShortcut() is supported. */
@WorkerThread
public static void prepareIsRequestPinShortcutSupported() {
diff --git a/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
new file mode 100644
index 0000000..e207449
--- /dev/null
+++ b/components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtilsTest.java
@@ -0,0 +1,182 @@
+// 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.components.webapps;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.Bitmap;
+import android.os.Build;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowApplication;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowShortcutManager;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Feature;
+
+import java.util.List;
+
+/** Tests WebappsUtils. */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = Build.VERSION_CODES.Q)
+public class WebappsUtilsTest {
+ private Context mContext;
+ private ShortcutManager mShortcutManager;
+ private ShadowShortcutManager mShadowShortcutManager;
+ private ShadowPackageManager mShadowPackageManager;
+ private ShadowApplication mShadowApplication;
+
+ @Before
+ public void setUp() {
+ mContext = ApplicationProvider.getApplicationContext();
+ ContextUtils.initApplicationContextForTests(mContext);
+ mShortcutManager = mContext.getSystemService(ShortcutManager.class);
+ mShadowShortcutManager = shadowOf(mShortcutManager);
+ mShadowPackageManager = shadowOf(mContext.getPackageManager());
+ mShadowApplication = shadowOf((Application) mContext);
+ WebappsUtils.setAddToHomeIntentSupportedForTesting(null); // Reset
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Webapp"})
+ public void testIsAddToHomeIntentSupported_ShortcutManagerSupported() {
+ mShadowShortcutManager.setIsRequestPinShortcutSupported(true);
+ assertTrue(WebappsUtils.isAddToHomeIntentSupported());
+ }
+
+ @Test
+ @SmallTest
+ @Feature({"Webapp"})
+ public void testIsAddToHomeIntentSupported_ShortcutManagerNotSupported() {
+ mShadowShortcutManager.setIsRequestPinShortcutSupported(false);
+ assertFalse(WebappsUtils.isAddToHomeIntentSupported());
Original Bug Report
Potential Leak of WebApp Launch MACs via Implicit Broadcast Fallback on Android
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: Chrome for Android’s fallback mechanism for adding webapp shortcuts uses an implicit broadcast that leaks sensitive authentication MACs to malicious apps. These MACs can be used to spoof trusted webapp launches and bypass intent security checks. This bypass potentially allows an attacker to trigger malicious image decoding within the high-privileged browser process.
Affected files:
components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.javachrome/android/java/src/org/chromium/chrome/browser/ShortcutHelper.javachrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.javachrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.javachrome/browser/android/shortcut_helper.ccchrome/browser/webapps/webapps_client_android.cccomponents/webapps/browser/android/add_to_homescreen_installer.ccchrome/browser/android/webapk/webapk_install_service.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Potential Security Issue
An investigation into Chrome’s webapp shortcut creation on Android has revealed a potential vulnerability where sensitive authentication secrets (HMAC-SHA256 MACs) can be leaked via an implicit broadcast. This leak occurs when Chrome falls back to a deprecated shortcut installation method on devices where the modern ShortcutManager API is not supported by the default launcher (common on Android Go, Android TV, or certain third-party launchers).
Technical Details
Chrome uses a secret HMAC-SHA256 MAC to verify that intents to launch webapps (via WebappLauncherActivity) originate from a trusted source (the launcher). This MAC is computed using a secret key stored in Chrome’s private data directory.
When adding a shortcut on affected devices, the fallback code in WebappsUtils.addShortcutToHomescreen sends an implicit broadcast with the action com.android.launcher.action.INSTALL_SHORTCUT.
File: components/webapps/browser/android/java/src/org/chromium/components/webapps/WebappsUtils.java
Code Path:
80: public static void addShortcutToHomescreen(
81: String id, String title, Bitmap icon, boolean isIconAdaptive, Intent shortcutIntent) {
82: if (isRequestPinShortcutSupported()) {
83: addShortcutWithShortcutManager(id, title, icon, isIconAdaptive, shortcutIntent);
84: return;
85: }
86:
87: Intent intent = createAddToHomeIntent(title, icon, shortcutIntent);
88: ContextUtils.getApplicationContext().sendBroadcast(intent); // Potential leak occurs here
89: showAddedToHomescreenToast(title);
90: }
The shortcutIntent passed into this broadcast contains the sensitive EXTRA_MAC extra. Because the broadcast is implicit and lacks any receiver package or permission constraints, any application that registers a receiver for this action at runtime (via Context.registerReceiver()) can intercept the intent and extract the URL and MAC.
Potential Impact
- Phishing and UI Spoofing: An attacker possessing a stolen MAC can launch
WebappLauncherActivityfor the intercepted URL with spoofed intent extras such as a malicious name or icon. This allows the attacker to present a site in a fullscreen standalone window that mimics a trusted application, with no URL bar for the user to verify the origin. - Browser Process RCE: When
WebappLauncherActivityreceives an intent with a valid MAC, it treats the intent data as trusted. This trust is inherited byWebappActivity, which then decodes the attacker-providedEXTRA_ICONstring usingBitmapFactory.decodeByteArraydirectly in the browser process. This bypasses Chrome’s standard security practice of decoding untrusted images in a sandboxed utility process, potentially allowing an attacker to exploit vulnerabilities in OS-level image codecs for Remote Code Execution (RCE) in the browser process.
File: chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIcon.java
Potential Vulnerable Decoding:
126: private @Nullable Bitmap generateBitmap() {
127: if (mEncoded != null && mIsTrusted) {
128: return BitmapHelper.decodeBitmapFromString(mEncoded);
...
Suggested Potential Steps to Reproduce
- Install a malicious app on an Android device that registers a receiver for
com.android.launcher.action.INSTALL_SHORTCUTat runtime. - Ensure the device is using a launcher that does not support the Pin Shortcut API.
- In Chrome, use the “Add to Home screen” menu option on a website.
- Observe the malicious app intercepting the broadcast and extracting the
EXTRA_MACandEXTRA_URLfrom the intent extras. - Craft a new intent to
WebappLauncherActivitywith the stolen MAC and URL, but substitute a malicious Base64 icon string inEXTRA_ICONand a spoofed name. - Confirm that Chrome launches the webapp with the spoofed identity and decodes the icon in the browser process.
Recommended Fix
The INSTALL_SHORTCUT fallback should be removed or made explicit (by targeting the default launcher) on Android 8.0 (O) and above. Additionally, the security model should be revised to ensure that icon data from external intents is always processed in a sandboxed utility process, regardless of whether a valid signature is provided.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.