Medium watch CVSS 7.5 chrome Policy Bypass 🔧 Commit mapped

Overview

Medium
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in PDF
DescriptionInsufficient policy enforcement in PDF
ComponentPDF
Bug ClassPolicy Bypass
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker478296121
Fix commit2265687230f6 (chromium/src) +65/-6
CWECWE-284
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedAyato Shitomi
Reported2026-01-23 (per vendor advisory)
Fix landed2026-02-06 (public git)
Disclosed2026-03-10
Patch gap32 days — fix public in source before the advisory

Background

PDF native page (Android)
Chrome on Android can show PDFs inline as a native page addressed by an internal URL such as chrome-native://pdf/link?url=<encoded original URL>; PdfUtils.decodePdfPageUrl recovers the original URL from it.
PDF re-download
when a finished navigation lands on such a PDF page URL that maybeShowNativePage cannot display (e.g. going back to it from a non-PDF page), TabImpl.handleDidFinishNavigation loads the decoded URL again to fetch the PDF.
Browser- vs. renderer-initiated navigation
LoadUrlParams built with only a URL is treated as browser-initiated with no initiator origin, which carries more trust (for example in SameSite=Strict cookie decisions and scheme restrictions) than a renderer-initiated navigation from a known origin.

Root Cause Analysis

In TabImpl.handleDidFinishNavigation, when maybeShowNativePage returned false, the code called PdfUtils.decodePdfPageUrl(url.getSpec()). Any non-null result was passed to loadUrl(new LoadUrlParams(downloadUrl)). That step had two flaws. First, the decoded URL was not restricted by scheme, so a PDF page URL whose embedded url parameter was something like chrome://version (the new PDF_LINK_ENCODED_INVALID test case) would be loaded. Second, the new load dropped the original navigation’s isRendererInitiated and initiator origin, so it ran as a fresh browser-initiated navigation. Per the commit and the new comment, that could escalate privileges and bypass SameSite=Strict cookie protection. The commit says the re-download can be triggered when the current page is not a PDF, so a page that is not a PDF can drive this path.

The patch makes three changes. It adds PdfUtils.getPdfReDownloadUrl, which returns the decoded URL only if it starts with http:// or https:// and otherwise returns null (so showRenderedPage() runs instead). It adds isRendererInitiated and initiatorOrigin parameters to handleDidFinishNavigation. It copies them onto the re-download LoadUrlParams with setIsRendererInitiated and setInitiatorOrigin. TabWebContentsObserver now passes navigation.isRendererInitiated() and navigation.getInitiatorOrigin().

Key insight
The PDF re-download step turned an attacker-influenced URL from a PDF page URL into a new browser-initiated navigation without checking its scheme or keeping its initiator. The fix allows only HTTP(S) re-download targets and carries over the original navigation’s renderer-initiated flag and initiator origin.

Attack Path

  1. Reach a PDF page URL a navigation in the tab finishes on a chrome-native://pdf/link?url=... style URL whose embedded target the attacker chose, for example via going back to a PDF URL from a non-PDF page as the commit describes. Exactly how the attacker gets this URL into the navigation is not shown in the diff (inferred).
  2. Native page not shown maybeShowNativePage returns false, so handleDidFinishNavigation decodes the embedded URL for a re-download.
  3. Laundered navigation before the patch, the decoded URL (of any scheme, e.g. chrome://version) was loaded with a bare LoadUrlParams, so it ran as a browser-initiated navigation with no initiator origin.
  4. Policy bypass this lets web content reach URLs or schemes it could not normally navigate to, and makes a cross-site request look browser-initiated so that SameSite=Strict cookies are sent. The commit names the cookie bypass; any further consequence on the reached page is (inferred).

Impact Assessment

Web content on Chrome for Android could make the browser start a navigation it should not be able to start: to non-HTTP(S) schemes such as chrome://, and as a browser-initiated navigation that gets around SameSite=Strict cookie protection. Preconditions: Android only (TabImpl, PdfUtils); the inline PDF path must be active (PdfUtils.shouldOpenPdfInline); and the tab must commit a PDF page URL that maybeShowNativePage won’t show, as with the back-navigation-to-PDF flow the commit describes. Whether this needs a user gesture is not shown. The vendor rates it medium severity (“Insufficient policy enforcement in PDF”).

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
modified
if
chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java
modified

Files Changed

  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
  • chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
  • chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java
  • chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtilsUnitTest.java

Audit Directions

  • Browser-side follow-up navigations
    look for other Android/Java code that reacts to a finished or failed navigation by calling loadUrl(new LoadUrlParams(url)), such as native-page fallbacks, offline pages, download retries or interstitial “try again” actions. Check that those loads keep isRendererInitiated, initiatorOrigin and referrer.
  • Encoded-URL wrappers
    audit internal wrapper URLs that embed a target URL in a query parameter (chrome-native://, chrome:// handlers, PdfUtils.encodePdfPageUrl counterparts). Check that the decoded target is limited by scheme before it is navigated to, opened or downloaded.
  • History-driven re-execution
    review session-history and back/forward restoration paths where a stored entry makes the browser start a new load. Check whether the stored URL or its initiator can be influenced by a page that is not the original initiator.
From 2265687230f6a3642e766a68d7c3b201ced0bbf6 Mon Sep 17 00:00:00 2001
From: Shu Yang <[email protected]>
Date: Fri, 06 Feb 2026 13:32:39 -0800
Subject: [PATCH] Restrict PDF re-downloads to safe schemes and preserve initiator

Going back to a PDF URL may require a re-download, and this can be
triggered when the current page is not a PDF. Ensure that these
navigations can't elevate their privileges by limiting to HTTP(S)
and preserving information about the initiator.

Bug: 478296121
Change-Id: Ic08a4aa797b0ba11f7010714570e4e9d3be31a48
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7520272
Reviewed-by: Charlie Reis <[email protected]>
Reviewed-by: Sirisha Kavuluru <[email protected]>
Commit-Queue: Shu Yang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1581094}
---

diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
index ae0b4dee..95f7a504 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabImpl.java
@@ -112,6 +112,7 @@
 import org.chromium.ui.base.ViewAndroidDelegate;
 import org.chromium.ui.base.WindowAndroid;
 import org.chromium.url.GURL;
+import org.chromium.url.Origin;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -1854,8 +1855,15 @@
      * @param url The URL that was loaded.
      * @param transitionType The transition type to the current URL.
      * @param isPdf Whether the navigation is for PDF content.
+     * @param isRendererInitiated Whether the navigation is initiated by renderer.
+     * @param initiatorOrigin The Origin that initiated this navigation.
      */
-    void handleDidFinishNavigation(GURL url, int transitionType, boolean isPdf) {
+    void handleDidFinishNavigation(
+            GURL url,
+            int transitionType,
+            boolean isPdf,
+            boolean isRendererInitiated,
+            @Nullable Origin initiatorOrigin) {
         mIsNativePageCommitPending = false;
         boolean isReload = (transitionType & PageTransition.CORE_MASK) == PageTransition.RELOAD;
         // Set isPdf param based on the url. This is because the isPdf param in NavigationHandle is
@@ -1866,11 +1874,20 @@
                 PdfUtils.shouldOpenPdfInline(isIncognito())
                         && PdfUtils.isDownloadedPdf(url.getSpec());
         if (!maybeShowNativePage(url.getSpec(), isReload, isPdf ? new PdfInfo() : null)) {
-            String downloadUrl = PdfUtils.decodePdfPageUrl(url.getSpec());
+            // This is restricted to HTTP(S) URLs specifically, as these are the only schemes that
+            // necessitate a PDF re-download.
+            String downloadUrl = PdfUtils.getPdfReDownloadUrl(url.getSpec());
             if (downloadUrl != null) {
-                // When the download url is not null, we are on a pdf native page which requires
-                // re-download. Load the download url to trigger the re-download.
-                loadUrl(new LoadUrlParams(downloadUrl));
+                // When the download url is not null, we are navigating to a pdf native page which
+                // requires re-download. Load the download url to trigger the re-download.
+                var param = new LoadUrlParams(downloadUrl);
+                // To avoid a SameSite=strict cookie bypass, it is important to preserve whether the
+                // navigation was renderer initiated and what its initiator origin was.
+                param.setIsRendererInitiated(isRendererInitiated);
+                if (initiatorOrigin != null) {
+                    param.setInitiatorOrigin(initiatorOrigin);
+                }
+                loadUrl(param);
             } else {
                 showRenderedPage();
             }
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
index eec3439..18b339eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/tab/TabWebContentsObserver.java
@@ -318,7 +318,11 @@
 
             mTab.updateTitle();
             mTab.handleDidFinishNavigation(
-                    navigation.getUrl(), navigation.pageTransition(), navigation.isPdf());
+                    navigation.getUrl(),
+                    navigation.pageTransition(),
+                    navigation.isPdf(),
+                    navigation.isRendererInitiated(),
+                    navigation.getInitiatorOrigin());
             mTab.setIsShowingErrorPage(navigation.isErrorPage());
 
             // TODO(crbug.com/40264745) remove this call. onUrlUpdated should have been called
diff --git a/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java b/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java
index 61383fa..222a9473 100644
--- a/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java
+++ b/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtils.java
@@ -316,6 +316,30 @@
     }
 
     /**
+     * Extracts a valid HTTP(S) URL from a PDF page URL for re-downloading.
+     *
+     * <p>This method decodes the provided {@code originalUrl} and verifies that the result uses a
+     * supported scheme (HTTP or HTTPS).
+     *
+     * @param originalUrl The original, potentially encoded, URL string to process.
+     * @return The decoded URL string if it is a valid HTTP(S) URL; {@code null} otherwise.
+     */
+    public static @Nullable String getPdfReDownloadUrl(String originalUrl) {
+        String decodedUrl = decodePdfPageUrl(originalUrl);
+
+        if (decodedUrl == null) {
+            return null;
+        }
+
+        if (decodedUrl.startsWith(UrlConstants.HTTP_URL_PREFIX)
+                || decodedUrl.startsWith(UrlConstants.HTTPS_URL_PREFIX)) {
+            return decodedUrl;
+        }
+
+        return null;
+    }
+
+    /**
      * Encode content uri if it is PDF MIME type.
      *
      * @param uri The uri to be encoded.
diff --git a/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtilsUnitTest.java b/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtilsUnitTest.java
index 91d1c9c..86c1629 100644
--- a/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtilsUnitTest.java
+++ b/chrome/browser/ui/android/pdf/java/src/org/chromium/chrome/browser/pdf/PdfUtilsUnitTest.java
@@ -49,6 +49,8 @@
     private static final String PDF_BLOB_URL = "blob:https://www.foo.com/abc";
     private static final String PDF_LINK_ENCODED =
             "chrome-native://pdf/link?url=https%3A%2F%2Fwww.foo.com%2Ftestfiles%2Fpdf%2Fsample.pdf";
+    private static final String PDF_LINK_ENCODED_INVALID =
+            "chrome-native://pdf/link?url=chrome%3A%2F%2Fversion";
     private static final String FILE_PATH = "/media/external/downloads/sample.pdf";
     private static final String FILE_NAME = "sample.pdf";
     private static final String IMAGE_FILE_URL = "file:///media/external/downloads/sample.jpg";
@@ -238,6 +240,18 @@
     }
 
     @Test
+    public void testGetPdfReDownloadUrl_Https() {
+        String downloadUrl = PdfUtils.getPdfReDownloadUrl(PDF_LINK_ENCODED);
+        Assert.assertEquals("The re-download url should match", PDF_LINK, downloadUrl);
+    }
+
+    @Test
+    public void testGetPdfReDownloadUrl_Invalid() {
+        String downloadUrl = PdfUtils.getPdfReDownloadUrl(PDF_LINK_ENCODED_INVALID);
+        Assert.assertNull("The re-download url should be null", downloadUrl);
+    }
+
+    @Test
     public void testEncodeDecodeUrlWithSpecialCharacter() {
         String encodedUrl = PdfUtils.encodePdfPageUrl(CONTENT_URL_SPECIAL_CHARACTER);
         String decodedUrl = PdfUtils.decodePdfPageUrl(encodedUrl);
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.