CVE-2026-11297
Overview
Files Changed
chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.javachrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManagerTest.java
Patch
From bd2a1c80bb4a02b6ffdfaac27ea3f28760aa8145 Mon Sep 17 00:00:00 2001 From: Evan Luo <[email protected]> Date: Tue, 14 Apr 2026 15:29:05 -0700 Subject: [PATCH] [reader mode] Fix incognito tab injection from reader mode intent extra Untrusted apps could bypass the normal checks for launch intents since the ReaderModeManager.isReaderModeCreatedIntent was not checking for wasIntentSenderChrome. External apps shouldn't be able to launch tabs in incognito, with this fix, it launches properly in non-incognito. Screen recording: http://screencast/cast/NTMzMjExNzk2ODkxMjM4NHxmZjFiYWMzMy00Nw Bug: 502502017 Change-Id: Ifa792bc48d3ab7ddc5f10be130fdfbcbec318234 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7763202 Reviewed-by: Brandon Wylie <[email protected]> Commit-Queue: Evan Luo <[email protected]> Cr-Commit-Position: refs/heads/main@{#1614752} --- diff --git a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java index 41eb73f..4eb8733b 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java @@ -1050,10 +1050,13 @@ /** * Determine if Reader Mode created the intent for a tab being created. + * * @param intent The Intent creating a new tab. * @return True whether the intent was created by Reader Mode. */ public static boolean isReaderModeCreatedIntent(Intent intent) { + // Ensure that the intent is from a trusted intent. + if (!IntentHandler.wasIntentSenderChrome(intent)) return false; int readerParentId = IntentUtils.safeGetIntExtra( intent, ReaderModeManager.EXTRA_READER_MODE_PARENT, Tab.INVALID_TAB_ID); diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManagerTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManagerTest.java index 6fcf4d7..1631342f 100644 --- a/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManagerTest.java +++ b/chrome/android/junit/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManagerTest.java @@ -19,6 +19,7 @@ import static org.mockito.Mockito.when; import android.app.Activity; +import android.content.Intent; import android.content.res.Configuration; import android.content.res.Resources; import android.os.Looper; @@ -38,7 +39,9 @@ import org.robolectric.Shadows; import org.chromium.base.Callback; +import org.chromium.base.ContextUtils; import org.chromium.base.FeatureOverrides; +import org.chromium.base.IntentUtils; import org.chromium.base.UnownedUserDataHost; import org.chromium.base.UserDataHost; import org.chromium.base.supplier.OneshotSupplierImpl; @@ -796,6 +799,31 @@ assertFalse(ReaderModeManager.shouldUseReaderModeMessages(mTab)); } + @Test + @Feature("ReaderMode") + public void testIsReaderModeCreatedIntent_NotChrome() { + Intent intent = new Intent(); + intent.putExtra(ReaderModeManager.EXTRA_READER_MODE_PARENT, 1); + + assertFalse( + "Untrusted intent should not be considered Reader Mode created", + ReaderModeManager.isReaderModeCreatedIntent(intent)); + } + + @Test + @Feature("ReaderMode") + public void testIsReaderModeCreatedIntent_FromChrome() { + Intent intent = new Intent(); + intent.putExtra(ReaderModeManager.EXTRA_READER_MODE_PARENT, 1); + + intent.setPackage(ContextUtils.getApplicationContext().getPackageName()); + IntentUtils.addTrustedIntentExtras(intent); + + assertTrue( + "Intent from Chrome should be considered Reader Mode created", + ReaderModeManager.isReaderModeCreatedIntent(intent)); + } + private NavigationEntry createNavigationEntry(int index, GURL url) { return new NavigationEntry( index, url, url, url, "", null, 0, 0, /* isInitialEntry= */ false);
Original Bug Report
Potential Incognito tab injection via spoofed Reader Mode intent extra
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 without the Chrome Security team.
Overview: An external Android application can bypass intent security checks by spoofing the EXTRA_READER_MODE_PARENT extra in a VIEW intent. This causes Chrome to process the untrusted intent through an internal Reader Mode code path, skipping launchUrlFromExternalApp. If the user currently has the Incognito tab model open, this allows the attacker to silently open an arbitrary URL in the user’s Incognito session.
Affected files:
chrome/android/java/src/org/chromium/chrome/browser/ChromeTabbedActivity.javachrome/android/java/src/org/chromium/chrome/browser/dom_distiller/ReaderModeManager.java
Estimated timestamp from git blame: 2025-06-30
Summary
There is a potential vulnerability in how Chrome for Android handles incoming intents in ChromeTabbedActivity. By appending a specific extra (org.chromium.chrome.browser.dom_distiller.EXTRA_READER_MODE_PARENT), an untrusted external application can trick Chrome into treating the intent as an internal Reader Mode transition. This bypasses standard external intent routing and allows the attacker to inject a URL into the currently active Tab Model, which can bypass the restriction against external apps opening URLs in Incognito mode.
Technical Details
When Chrome receives an android.intent.action.VIEW intent, it is eventually processed by ChromeTabbedActivity.launchIntent.
This method first checks if the intent originated from a trusted Chrome sender (IntentHandler.wasIntentSenderChrome(intent)). If it did not, execution skips the trusted block.
However, execution then proceeds to the following check:
if (ReaderModeManager.isEnabled() && ReaderModeManager.isReaderModeCreatedIntent(intent)) {
ReaderModeManager.isReaderModeCreatedIntent(intent) simply checks for the presence of the EXTRA_READER_MODE_PARENT extra and ensures it is not equal to Tab.INVALID_TAB_ID. It does not verify the sender of the intent.
If the extra is present, the code executes:
return getCurrentTabCreator().createNewTab(...);
getCurrentTabCreator() returns the TabCreator for whichever Tab Model (Standard or Incognito) is currently active on the screen. This completely bypasses the intended fallback behavior at the end of the method:
Tab tab = getTabCreator(false).launchUrlFromExternalApp(...);
launchUrlFromExternalApp is the secure chokepoint for external intents and contains an explicit assertion (assert !mIncognito) to prevent external apps from opening tabs in Incognito.
Impact
If a user is actively browsing in an Incognito tab, or switches away from Chrome while an Incognito tab is active, an attacker application can send a spoofed intent that silently opens an arbitrary URL within that Incognito session. This violates the security boundary that prevents untrusted external applications from interacting with the user’s private browsing session.
Potential Reproduction Steps
Note: These steps are suggested based on code analysis, as a working Proof of Concept has not yet been executed.
- Open Chrome on Android and switch to the Incognito tab model (e.g., open a new Incognito tab).
- Press the home button to background Chrome, leaving Incognito as the active model.
- Using an unprivileged app or
adb, send a VIEW intent with the spoofed extra:adb shell am start -n com.android.chrome/org.chromium.chrome.browser.ChromeTabbedActivity \ -a android.intent.action.VIEW \ -d "https://example.com" \ --ei org.chromium.chrome.browser.dom_distiller.EXTRA_READER_MODE_PARENT 0 - Observe that Chrome opens
https://example.comin the Incognito profile.
Suggested Fix
Update the logic to ensure that Reader Mode intents are only processed if they originate from Chrome. This can be done by modifying ChromeTabbedActivity.launchIntent to check the sender, or by updating ReaderModeManager.isReaderModeCreatedIntent:
public static boolean isReaderModeCreatedIntent(Intent intent) {
if (!IntentHandler.wasIntentSenderChrome(intent)) return false;
int readerParentId =
IntentUtils.safeGetIntExtra(
intent, ReaderModeManager.EXTRA_READER_MODE_PARENT, Tab.INVALID_TAB_ID);
return readerParentId != Tab.INVALID_TAB_ID;
}
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.