Low watch CVSS 5.1 chrome Info Disclosure 🔧 Commit mapped

Overview

Low
Severity
5.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Sharing
DescriptionInformation leak in Sharing
ComponentSharing
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
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.
Tracker497232609
Fix commit6146f6b618ea (chromium/src) +80/-8
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-03-28 (per vendor advisory)
Fix landed2026-07-06 (public git)
Disclosed2026-08-25
Patch gap50 days — fix public in source before the advisory

Background

Android PDF page URL
Chrome on Android shows PDFs in a native page whose visible URL encodes the underlying document URI. PdfUtils.decodePdfPageUrl recovers that URI (for example a content:// URI), and PdfUtils.isDownloadedPdf decides whether the page counts as a downloaded or local PDF.
Share intent with file URIs
when ShareParams carries setFileUris(...), the Android share sheet sends those content:// URIs to the app the user picks. Per the commit message, it also grants that app read permission on them.
Chrome's `FileProvider`
Chrome’s own content provider (content://org.chromium.chrome.FileProvider/...), which can expose private app files such as exported passwords (passwords/ChromePass.csv in the test) or netlogs. External apps must never be granted access to it arbitrarily.
`PdfUtils.isUriSafeForSharing`
a check the patch calls (it is not defined in this diff). Per the commit message it allows only safe content URI paths, such as downloaded PDFs, and rejects Chrome’s private directories.

Root Cause Analysis

In ShareDelegateImpl.java, the share path decided whether to share a PDF as a file using only PdfUtils.isDownloadedPdf(visibleUrl.getSpec()). If that was true, it decoded the embedded URI with PdfUtils.decodePdfPageUrl and placed it directly into setFileUris(...) with MimeTypeUtils.PDF_MIME_TYPE. Nothing checked what that URI pointed to. The commit message explains that an external app could make Chrome navigate to a PDF page URL wrapping a nested content URI into Chrome’s private FileProvider. A later user share would then hand that private URI, with read permission, to whichever app the user chose. The violated rule is that Chrome should only grant other apps read access to content it intends to share, never to its private storage.

The fix requires both isDownloadedPdf and PdfUtils.isUriSafeForSharing(pdfUri, mContext) before attaching a file URI. getUrlToShare now returns the visible URL (a plain-URL share, with no file grant) instead of "" when the decoded URI is unsafe. The new unit test confirms that getFileUris() is null and the URL is the PDF page URL for content://org.chromium.chrome.FileProvider/passwords/ChromePass.csv.

Key insight
The share code trusted a URI decoded from the page URL just because the page was classed as a “downloaded PDF”, and shared it with read permission without checking where it pointed. The fix validates the decoded URI with PdfUtils.isUriSafeForSharing and falls back to a URL-only share without a permission grant.

Attack Path

  1. Malicious app launches Chrome an installed external app on the device makes Chrome navigate to a PDF page URL that encodes a nested content URI into Chrome’s private FileProvider (per the commit message). This requires a malicious app on the device, not just web content.
  2. Page is classed as a downloaded PDF PdfUtils.isDownloadedPdf accepts the visible URL, so the share path treats the decoded inner URI as the PDF file to share.
  3. User shares the page the user shares the tab (for example from the overflow menu, ShareOrigin.OVERFLOW_MENU in the test). Before the fix, ShareParams carried the private content:// URI as a file URI.
  4. Target app receives read access if the user picks the attacker’s app in the share sheet, it receives the URI with read permission. It can then read the private file, such as exported passwords or netlogs named in the commit message, if such a file exists at that path.

Impact Assessment

The advisory describes “Information leak in Sharing” and gives no in-the-wild status. On Android, a malicious installed app could get read access to files behind Chrome’s private FileProvider, which the commit message says could include password exports or netlogs. The preconditions are a malicious app on the device that can launch Chrome to a crafted PDF page URL, the user sharing that page, and the user choosing the malicious app in the share sheet. Whether a sensitive file actually exists at the targeted path depends on prior user actions (inferred).

Changed Functions

FunctionChangeNotes
if
chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
modified

Files Changed

  • chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplTest.java
  • chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
  • chrome/android/junit/src/org/chromium/chrome/browser/share/ShareDelegateImplUnitTest.java

Audit Directions

  • Decoded or nested URIs reaching intents
    look for Android code that unwraps a URI from a Chrome URL (PDF pages, offline pages, content:// viewers, download UI) and passes it to Intent extras, ClipData, or setFileUris without checking the authority and path against an allow-list.
  • Permission grants on share or open
    review every place Chrome attaches FLAG_GRANT_READ_URI_PERMISSION or file URIs (share, “open with”, print, download-complete notifications) for whether the URI could point into Chrome’s own FileProvider paths.
  • External-intent-controlled navigation
    examine how external apps can steer Chrome to internal or native-page URLs (PDF, distiller, offline) that later get trusted by privileged UI flows. Classify these by provenance, not just by URL shape.
From 6146f6b618ea43cec28efe58c40b456391b8944b Mon Sep 17 00:00:00 2001
From: Shu Yang <[email protected]>
Date: Mon, 06 Jul 2026 16:01:21 -0700
Subject: [PATCH] Restrict shared PDF content URIs to safe paths

An external app could trigger navigation to a nested content URI
pointing to Chrome's private file provider. If the user subsequently
shared this page, Chrome would include the private URI in the share
intent with read permission, allowing potential exfiltration of
sensitive files (e.g. passwords, netlogs) if the user selected the
malicious app in the share sheet.

This CL mitigates this by validating the PDF document URI before
sharing it. We use PdfUtils.isUriSafeForSharing to allow only
safe content URI paths (like downloaded PDFs) and block access to
Chrome's private directories. Unsafe URIs will fallback to a plain
URL share without granting read permissions.

Bug: 497232609
Change-Id: I1b63a2d0a7254571f80e788a26e7fff3871bd07d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8040877
Reviewed-by: Sirisha Kavuluru <[email protected]>
Commit-Queue: Shu Yang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1657542}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
index a66190f..e6f25a2 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/share/ShareDelegateImpl.java
@@ -259,13 +259,17 @@
             @ShareOrigin final int shareOrigin,
             final boolean shareDirectly) {
         ShareParams.Builder shareParamsBuilder =
-                new ShareParams.Builder(window, title, getUrlToShare(visibleUrl));
+                new ShareParams.Builder(window, title, getUrlToShare(visibleUrl, mContext));
 
         shareParamsBuilder.setOrigin(shareOrigin);
-        boolean isDownloadedPdf = PdfUtils.isDownloadedPdf(visibleUrl.getSpec());
-        if (isDownloadedPdf) {
+        String decodedUrl = PdfUtils.decodePdfPageUrl(visibleUrl.getSpec());
+        Uri pdfUri = decodedUrl != null ? Uri.parse(decodedUrl) : null;
+        boolean isDownloadedPdf =
+                PdfUtils.isDownloadedPdf(visibleUrl.getSpec())
+                        && PdfUtils.isUriSafeForSharing(pdfUri, mContext);
+        if (isDownloadedPdf && pdfUri != null) {
             ArrayList<Uri> fileToShare = new ArrayList<>();
-            fileToShare.add(Uri.parse(PdfUtils.decodePdfPageUrl(visibleUrl.getSpec())));
+            fileToShare.add(pdfUri);
             shareParamsBuilder
                     .setFileUris(fileToShare)
                     .setFileContentType(MimeTypeUtils.PDF_MIME_TYPE);
@@ -285,8 +289,14 @@
     }
 
     @VisibleForTesting
-    static String getUrlToShare(GURL visibleUrl) {
-        if (PdfUtils.isDownloadedPdf(visibleUrl.getSpec())) return "";
+    static String getUrlToShare(GURL visibleUrl, @Nullable Context context) {
+        if (PdfUtils.isDownloadedPdf(visibleUrl.getSpec())) {
+            String decodedUrl = PdfUtils.decodePdfPageUrl(visibleUrl.getSpec());
+            Uri pdfUri = decodedUrl != null ? Uri.parse(decodedUrl) : null;
+            if (context == null || PdfUtils.isUriSafeForSharing(pdfUri, context)) {
+                return "";
+            }
+        }
         return visibleUrl.getSpec();
     }
 
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplTest.java
index a86b0aa..8294157e 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/share/ShareDelegateImplTest.java
@@ -4,6 +4,9 @@
 
 package org.chromium.chrome.browser.share;
 
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Assert;
@@ -22,9 +25,10 @@
     @Test
     @SmallTest
     public void testGetUrlToShare() {
-        Assert.assertEquals("", ShareDelegateImpl.getUrlToShare(GURL.emptyGURL()));
+        Context context = ApplicationProvider.getApplicationContext();
+        Assert.assertEquals("", ShareDelegateImpl.getUrlToShare(GURL.emptyGURL(), context));
 
         final GURL httpsUrl = new GURL("https://blah.com");
-        Assert.assertEquals(httpsUrl.getSpec(), ShareDelegateImpl.getUrlToShare(httpsUrl));
+        Assert.assertEquals(httpsUrl.getSpec(), ShareDelegateImpl.getUrlToShare(httpsUrl, context));
     }
 }
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareDelegateImplUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareDelegateImplUnitTest.java
index 20dc99c3..06e6938a 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareDelegateImplUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/share/ShareDelegateImplUnitTest.java
@@ -17,6 +17,8 @@
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.Build;
 
@@ -61,6 +63,8 @@
 import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
 import org.chromium.components.browser_ui.share.ShareParams;
 import org.chromium.components.browser_ui.util.AutomotiveUtils;
+import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
+import org.chromium.components.dom_distiller.core.DomDistillerUrlUtilsJni;
 import org.chromium.components.favicon.LargeIconBridgeJni;
 import org.chromium.components.feature_engagement.Tracker;
 import org.chromium.content_public.browser.RenderFrameHost;
@@ -87,6 +91,7 @@
             new OverrideContextWrapperTestRule();
 
     @Mock private Context mContext;
+    @Mock private PackageManager mPackageManager;
     @Mock private RenderFrameHost mRenderFrameHost;
     @Mock private BottomSheetController mBottomSheetController;
     @Mock private ShareSheetDelegate mShareSheetController;
@@ -105,6 +110,7 @@
     @Mock SnackbarManager mSnackbarManager;
 
     @Mock private DataProtectionBridge.Natives mDataProtectionBridgeMock;
+    @Mock private DomDistillerUrlUtils.Natives mDomDistillerUrlUtilsJniMock;
 
     private final ArgumentCaptor<ShareParams> mShareParamsCaptor =
             ArgumentCaptor.forClass(ShareParams.class);
@@ -166,6 +172,12 @@
         TrackerFactory.setTrackerForTests(mTracker);
         Mockito.doReturn(new WeakReference<>(mActivity)).when(mWindowAndroid).getActivity();
         DataProtectionBridge.setInstanceForTesting(mDataProtectionBridgeMock);
+        DomDistillerUrlUtilsJni.setInstanceForTesting(mDomDistillerUrlUtilsJniMock);
+        doAnswer(invocation -> new GURL((String) invocation.getArgument(0)))
+                .when(mDomDistillerUrlUtilsJniMock)
+                .getOriginalUrlFromDistillerUrl(anyString());
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+        doReturn("org.chromium.chrome").when(mContext).getPackageName();
 
         // TODO(crbug.com/406591712): Update to stubbing share methods when those are added.
         doAnswer(sShareIsAllowedByPolicy)
@@ -735,4 +747,50 @@
                 "Page title should be set on ShareParams.", pdfTitle, params.getTitle());
         Assert.assertEquals("URL should be empty on ShareParams.", "", params.getUrl());
     }
+
+    @Test
+    public void testShareUnsafePDf() {
+        final String pdfTitle = "unsafe.pdf";
+        final String contentUri =
+                "content://org.chromium.chrome.FileProvider/passwords/ChromePass.csv";
+        final String pdfUrl = PdfUtils.encodePdfPageUrl(contentUri);
+        doReturn(true).when(mTab).isNativePage();
+        doReturn(new GURL(pdfUrl)).when(mTab).getUrl();
+        doReturn(pdfTitle).when(mTab).getTitle();
+        doReturn(mock(WindowAndroid.class)).when(mTab).getWindowAndroid();
+
+        // Setup mock package manager to identify this URI as coming from this app.
+        ProviderInfo providerInfo = new ProviderInfo();
+        providerInfo.packageName = "org.chromium.chrome";
+        doReturn(providerInfo)
+                .when(mPackageManager)
+                .resolveContentProvider("org.chromium.chrome.FileProvider", 0);
+
+        createShareDelegate(false, mShareSheetController);
+        mShareDelegate.share(mTab, false, ShareOrigin.OVERFLOW_MENU);
+        verify(mShareSheetController)
+                .share(
+                        mShareParamsCaptor.capture(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        any(),
+                        anyInt(),
+                        anyLong(),
+                        anyBoolean(),
+                        any(),
+                        any(),
+                        any(),
+                        any());
+
+        ShareParams params = mShareParamsCaptor.getValue();
+        // Should NOT be shared as file because it is unsafe.
+        Assert.assertNull("File URIs should be null for unsafe PDF.", params.getFileUris());
+        // Should be shared as URL instead.
+        Assert.assertEquals("URL should be the visible PDF URL.", pdfUrl, params.getUrl());
+    }
 }
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.