Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactCryptographic Flaw in WebAppInstalls
DescriptionCryptographic Flaw in WebAppInstalls
ComponentWebAppInstalls
Bug ClassLogic Error
Tracker521963740
Fix commite0cd2f8abf21 (chromium/src) +38/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
  • chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java
From e0cd2f8abf21f75be7d17a81c87c9c4d69c0fb44 Mon Sep 17 00:00:00 2001
From: Dan Murphy <dmurph@chromium.org>
Date: Thu, 25 Jun 2026 11:33:45 -0700
Subject: [PATCH] [PWA] Use length-prefixed encoding in WebappAuthenticator MAC

WebappAuthenticator.getMacForUrlAndIcon computed HMAC over sequentially
concatenated URL and icon strings without delimiters. A malicious local
app could shift data between URL and icon fields while maintaining a
valid MAC, forcing the unsandboxed browser process to decode untrusted
image payloads.

We replace ad-hoc concatenation with standard Java DataOutputStream
length-prefixed binary serialization (writeInt + write). This ensures
field boundaries are mathematically unambiguous without imposing 64KB
UTF-8 limits.

Existing shortcuts created prior to this change fall back to URL-only
verification (MAC_LEGACY), maintaining homescreen navigation while
marking icons as untrusted.

TAG=agy
CONV=9d6b609e-3656-4e36-bdd7-50a54fb71f56

Fixed: b:521963740
Test: run_chrome_junit_tests --gtest_filter=*WebappAuthenticatorTest*
Change-Id: I8842b5ec53a86a98e16af0953358dec1823f45c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7997269
Reviewed-by: Glenn Hartmann <hartmanng@chromium.org>
Commit-Queue: Daniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1652579}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
index d463359..a4cc4a03 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
@@ -12,6 +12,8 @@
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -86,11 +88,26 @@
         if (mac == null) {
             return null;
         }
-        mac.update(ApiCompatibilityUtils.getBytesUtf8(url));
-        if (encodedIcon != null) {
-            mac.update(ApiCompatibilityUtils.getBytesUtf8(encodedIcon));
+        if (encodedIcon == null) {
+            mac.update(ApiCompatibilityUtils.getBytesUtf8(url));
+            return mac.doFinal();
         }
-        return mac.doFinal();
+        try {
+            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+            DataOutputStream dataStream = new DataOutputStream(byteStream);
+            byte[] urlBytes = ApiCompatibilityUtils.getBytesUtf8(url);
+            dataStream.writeInt(urlBytes.length);
+            dataStream.write(urlBytes);
+            byte[] iconBytes = ApiCompatibilityUtils.getBytesUtf8(encodedIcon);
+            dataStream.writeInt(iconBytes.length);
+            dataStream.write(iconBytes);
+            dataStream.flush();
+            mac.update(byteStream.toByteArray());
+            return mac.doFinal();
+        } catch (IOException e) {
+            Log.w(TAG, "Error serializing MAC data", e);
+            return null;
+        }
     }
 
     /**
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java
index 7525c30..8861d22 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/webapps/WebappAuthenticatorTest.java
@@ -73,4 +73,21 @@
                 WebappAuthenticator.MAC_INVALID,
                 WebappAuthenticator.verifyMac(url, null, macWithIcon));
     }
+
+    @Test
+    @SmallTest
+    @Feature({"Webapps"})
+    public void testAuthenticationFieldBoundaries() {
+        String url = "https://attacker.example/app/";
+        String icon = "PAYLOAD_LEGIT_ICON";
+        byte[] mac = WebappAuthenticator.getMacForUrlAndIcon(url, icon);
+        Assert.assertNotNull(mac);
+
+        // Shift boundary between url and icon
+        String shiftedUrl = "https://attacker.example/app/PAYLOAD_";
+        String shiftedIcon = "LEGIT_ICON";
+        Assert.assertEquals(
+                WebappAuthenticator.MAC_INVALID,
+                WebappAuthenticator.verifyMac(shiftedUrl, shiftedIcon, mac));
+    }
 }
Loading diff…

Original Bug Report

reported by rj...@google.com

MAC Delimiter Injection in WebappAuthenticator allows forging trusted icons

Flapjack, 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: WebappAuthenticator.java calculates a MAC over a URL and an icon sequentially without a delimiter. A malicious launcher application can exploit this to shift data from the URL to the icon field while maintaining a valid MAC, forcing the unsandboxed Chrome Browser process to decode an untrusted image payload.

Affected files:

  • chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappAuthenticator.java
  • chrome/android/java/src/org/chromium/chrome/browser/webapps/WebappLauncherActivity.java
  • chrome/browser/android/browserservices/intents/java/src/org/chromium/chrome/browser/browserservices/intents/WebappIcon.java

Estimated timestamp from git blame: Unknown (Google3 checkout)

Description

A potential MAC Delimiter Injection vulnerability exists in WebappAuthenticator.java due to the lack of delimiters between fields during HMAC calculation. When creating a homescreen shortcut for a Progressive Web App (PWA), Chrome generates a MAC to authenticate the shortcut intent. This prevents unauthorized apps from launching Chrome activities with arbitrary data.

However, in WebappAuthenticator.getMacForUrlAndIcon(String url, String encodedIcon), the strings are fed into the HMAC sequentially:

        mac.update(ApiCompatibilityUtils.getBytesUtf8(url));
        if (encodedIcon != null) {
            mac.update(ApiCompatibilityUtils.getBytesUtf8(encodedIcon));
        }

Because there is no delimiter, HMAC(URL + ICON) will produce the identical MAC for any (URL, ICON) pair as long as their contiguous concatenation remains the same.

Potential Attack Scenario

An attacker can exploit this flaw to bypass the trusted icon check:

  1. Preparation: The attacker hosts a PWA where the manifest defines a start_url containing a malicious image payload appended to the path (e.g., https://attacker.com/PAYLOAD). The manifest also defines a legitimate base64 icon (LEGIT_ICON).
  2. Installation & MAC Generation: The victim installs the PWA. Chrome generates a valid MAC for url="https://attacker.com/PAYLOAD" and icon="LEGIT_ICON". This MAC is sent to the Android system to pin the shortcut.
  3. MAC Acquisition: A malicious app on the victim’s device (e.g., acting as the default Launcher) intercepts the shortcut creation intent and extracts the valid MAC.
  4. Forging the Intent: The malicious app fires an Intent to WebappLauncherActivity.ACTION_START_WEBAPP with shifted boundaries:
    • EXTRA_URL: https://attacker.com/
    • EXTRA_ICON: PAYLOADLEGIT_ICON
    • EXTRA_MAC: The valid MAC obtained in Step 3.
  5. Verification Bypass: WebappLauncherActivity calls WebappAuthenticator.verifyMac. The method calculates the MAC for https://attacker.com/ + PAYLOADLEGIT_ICON. Because this concatenation matches the original input (https://attacker.com/PAYLOADLEGIT_ICON), the MAC is successfully validated as MAC_TRUSTED.
  6. Unsandboxed Execution: Because the MAC is trusted, WebappLauncherActivity sets EXTRA_IS_ICON_TRUSTED = true. Later, WebappIcon.java blindly decodes the malicious EXTRA_ICON payload in the highly-privileged, unsandboxed Chrome Browser process using Android’s native BitmapFactory.decodeByteArray.

(Note: These are suggested steps based on static analysis; our tooling cannot run code to produce a working exploit).

Impact

This vulnerability allows a local malicious application (typically a custom launcher) to force the unsandboxed Chrome Browser process to parse untrusted, attacker-controlled image data. If the image payload is crafted to exploit a vulnerability in underlying native image decoders (e.g., Skia or Android graphics libraries), it could lead to Remote Code Execution (RCE) and a complete sandbox escape.

Suggested Fix

Include a robust delimiter (such as a null byte or a length prefix) between the URL and the icon bytes when updating the HMAC state in WebappAuthenticator.java.

        mac.update(ApiCompatibilityUtils.getBytesUtf8(url));
        if (encodedIcon != null) {
            mac.update((byte) 0); // Null byte delimiter
            mac.update(ApiCompatibilityUtils.getBytesUtf8(encodedIcon));
        }

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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.

View on issue tracker