Chrome · PDF › Mime_handler
CVE-2026-110136
UAF in PDF (Mime_handler)
Overview
Critical
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fextensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc |
modified |
Files Changed
extensions/browser/mime_handler/mime_handler_stream_manager.ccchrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.ccextensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
Patch
From cb21df9dd4c5b550d530cd1f461731f0d1d25ad0 Mon Sep 17 00:00:00 2001
From: Andy Phan <andyphan@chromium.org>
Date: Mon, 05 Oct 2026 11:01:24 -0700
Subject: [PATCH] [mime_handler] Return early for uncommitted navigations
Return early in MimeHandlerStreamManager::DidFinishNavigation() when the
navigation has not committed, after cleaning up any matching unclaimed
stream. Otherwise, an uncommitted about:blank navigation in a child
frame can be treated as the placeholder extension frame and trigger a
new navigation during renderer process exit.
TAG=agy
CONV=1785936f-edea-495c-86d0-6ce822657ace
Bug: 568719364
Change-Id: Ibfc8976936514e76d35825ee8f1fbfc2d31ab00c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8511404
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Commit-Queue: Andy Phan <andyphan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1711495}
---
diff --git a/extensions/browser/mime_handler/mime_handler_stream_manager.cc b/extensions/browser/mime_handler/mime_handler_stream_manager.cc
index 578062c..b074961f 100644
--- a/extensions/browser/mime_handler/mime_handler_stream_manager.cc
+++ b/extensions/browser/mime_handler/mime_handler_stream_manager.cc
@@ -634,8 +634,8 @@
iter->second->navigation_id() == navigation_handle->GetNavigationId()) {
// This call may delete `this`.
DeleteUnclaimedStreamInfo(frame_tree_node_id);
- return;
}
+ return;
}
// Maybe set up postMessage support after the PDF content host finishes
@@ -668,7 +668,6 @@
if (url == pdf_extension_url &&
stream_info->extension_host_frame_tree_node_id() ==
frame_tree_node_id &&
- navigation_handle->HasCommitted() &&
!navigation_handle->IsErrorPage()) {
stream_info->SetDidExtensionFinishNavigation();
stream_info->delegate()->OnExtensionFrameFinished(navigation_handle,
diff --git a/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc b/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
index efed2df..c31b1881 100644
--- a/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
+++ b/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
@@ -300,6 +300,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle1(pdf_url,
pdf_host1);
+ navigation_handle1.set_has_committed(true);
ON_CALL(navigation_handle1, IsPdf).WillByDefault(Return(true));
// Before processing the initial content navigation handle, "reload" the URL.
@@ -322,6 +323,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle2(pdf_url,
pdf_host2);
+ navigation_handle2.set_has_committed(true);
ON_CALL(navigation_handle2, IsPdf).WillByDefault(Return(true));
EXPECT_FALSE(manager->DidContentFrameFinishNavigation(embedder_host));
diff --git a/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc b/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
index 9fcb17c..f6dd03c 100644
--- a/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
+++ b/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
@@ -6,6 +6,7 @@
#include <memory>
+#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
@@ -23,8 +24,10 @@
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/mime_handler/generic_mime_handler_stream_delegate.h"
@@ -1192,6 +1195,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle(
stream_info->stream()->original_url(), content_host);
+ navigation_handle.set_has_committed(true);
ON_CALL(navigation_handle, IsPdf).WillByDefault(Return(true));
ON_CALL(*delegate_ptr, ShouldSetUpPostMessage())
@@ -1222,6 +1226,51 @@
EXPECT_FALSE(manager->DidExtensionFrameFinishNavigation(embedder_host));
}
+// An uncommitted about:blank navigation in a child frame of the embedder host
+// aborted when its renderer process exits must not be treated as the
+// placeholder extension frame or trigger a navigation to the handler URL
+// (which would discard and free the speculative RenderFrameHost while
+// RenderFrameHostImpl::RenderProcessGone() is still on the stack).
+TEST_F(MimeHandlerStreamManagerTest,
+ DidFinishNavigationIgnoresUncommittedAboutBlankChildNavigation) {
+ content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+ auto* embedder_host = NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1));
+ auto* initiator_host =
+ CreateAndNavigateChild(embedder_host, GURL(kOriginalUrl2));
+ auto* child_host =
+ CreateAndNavigateChild(embedder_host, GURL("https://original_url3"));
+ ASSERT_NE(embedder_host->GetProcess(), initiator_host->GetProcess());
+ ASSERT_NE(initiator_host->GetProcess(), child_host->GetProcess());
+
+ SetUpSimpleStream(embedder_host, /*container_number=*/1, /*embedded=*/true);
+ MimeHandlerStreamManager* manager = mime_handler_stream_manager();
+ auto* stream_info = manager->GetClaimedStreamInfoForTesting(embedder_host);
+ ASSERT_TRUE(stream_info);
+
+ // Start a cross-process about:blank navigation in `child_host` with
+ // `initiator_host`'s SiteInstance and origin. Because about:blank does not
+ // use a URL loader, LoadURLWithParams() synchronously advances it to
+ // ReadyToCommitNavigation() on a speculative RenderFrameHost in
+ // `initiator_host`'s process.
+ content::NavigationController::LoadURLParams params{
+ GURL(url::kAboutBlankURL)};
+ params.frame_tree_node_id = child_host->GetFrameTreeNodeId();
+ params.source_site_instance = initiator_host->GetSiteInstance();
+ params.initiator_origin = initiator_host->GetLastCommittedOrigin();
+ params.is_renderer_initiated = true;
+ web_contents()->GetController().LoadURLWithParams(params);
+
+ // Simulate `initiator_host`'s renderer process (which hosts `child_host`'s
+ // speculative RenderFrameHost) crashing while the about:blank navigation is
+ // uncommitted.
+ static_cast<content::MockRenderProcessHost*>(initiator_host->GetProcess())
+ ->SimulateCrash();
+
+ EXPECT_FALSE(stream_info->DidExtensionStartNavigation());
+ EXPECT_FALSE(stream_info->extension_host_frame_tree_node_id());
+ EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
+}
+
// Verify `MimeHandlerStreamManager::PluginCanSave()` defaults to false
// and `MimeHandlerStreamManager::SetPluginCanSave()` updates the value.
TEST_F(MimeHandlerStreamManagerTest, PluginCanSave) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc b/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
index efed2df..c31b1881 100644
--- a/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
+++ b/chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
@@ -300,6 +300,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle1(pdf_url,
pdf_host1);
+ navigation_handle1.set_has_committed(true);
ON_CALL(navigation_handle1, IsPdf).WillByDefault(Return(true));
// Before processing the initial content navigation handle, "reload" the URL.
@@ -322,6 +323,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle2(pdf_url,
pdf_host2);
+ navigation_handle2.set_has_committed(true);
ON_CALL(navigation_handle2, IsPdf).WillByDefault(Return(true));
EXPECT_FALSE(manager->DidContentFrameFinishNavigation(embedder_host));
diff --git a/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc b/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
index 9fcb17c..f6dd03c 100644
--- a/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
+++ b/extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
@@ -6,6 +6,7 @@
#include <memory>
+#include "base/command_line.h"
#include "base/memory/weak_ptr.h"
#include "base/test/bind.h"
#include "base/test/gtest_util.h"
@@ -23,8 +24,10 @@
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/mock_navigation_handle.h"
+#include "content/public/test/mock_render_process_host.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
+#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/browser/mime_handler/generic_mime_handler_stream_delegate.h"
@@ -1192,6 +1195,7 @@
NiceMock<content::MockNavigationHandle> navigation_handle(
stream_info->stream()->original_url(), content_host);
+ navigation_handle.set_has_committed(true);
ON_CALL(navigation_handle, IsPdf).WillByDefault(Return(true));
ON_CALL(*delegate_ptr, ShouldSetUpPostMessage())
@@ -1222,6 +1226,51 @@
EXPECT_FALSE(manager->DidExtensionFrameFinishNavigation(embedder_host));
}
+// An uncommitted about:blank navigation in a child frame of the embedder host
+// aborted when its renderer process exits must not be treated as the
+// placeholder extension frame or trigger a navigation to the handler URL
+// (which would discard and free the speculative RenderFrameHost while
+// RenderFrameHostImpl::RenderProcessGone() is still on the stack).
+TEST_F(MimeHandlerStreamManagerTest,
+ DidFinishNavigationIgnoresUncommittedAboutBlankChildNavigation) {
+ content::IsolateAllSitesForTesting(base::CommandLine::ForCurrentProcess());
+ auto* embedder_host = NavigateAndCommit(main_rfh(), GURL(kOriginalUrl1));
+ auto* initiator_host =
+ CreateAndNavigateChild(embedder_host, GURL(kOriginalUrl2));
+ auto* child_host =
+ CreateAndNavigateChild(embedder_host, GURL("https://original_url3"));
+ ASSERT_NE(embedder_host->GetProcess(), initiator_host->GetProcess());
+ ASSERT_NE(initiator_host->GetProcess(), child_host->GetProcess());
+
+ SetUpSimpleStream(embedder_host, /*container_number=*/1, /*embedded=*/true);
+ MimeHandlerStreamManager* manager = mime_handler_stream_manager();
+ auto* stream_info = manager->GetClaimedStreamInfoForTesting(embedder_host);
+ ASSERT_TRUE(stream_info);
+
+ // Start a cross-process about:blank navigation in `child_host` with
+ // `initiator_host`'s SiteInstance and origin. Because about:blank does not
+ // use a URL loader, LoadURLWithParams() synchronously advances it to
+ // ReadyToCommitNavigation() on a speculative RenderFrameHost in
+ // `initiator_host`'s process.
+ content::NavigationController::LoadURLParams params{
+ GURL(url::kAboutBlankURL)};
+ params.frame_tree_node_id = child_host->GetFrameTreeNodeId();
+ params.source_site_instance = initiator_host->GetSiteInstance();
+ params.initiator_origin = initiator_host->GetLastCommittedOrigin();
+ params.is_renderer_initiated = true;
+ web_contents()->GetController().LoadURLWithParams(params);
+
+ // Simulate `initiator_host`'s renderer process (which hosts `child_host`'s
+ // speculative RenderFrameHost) crashing while the about:blank navigation is
+ // uncommitted.
+ static_cast<content::MockRenderProcessHost*>(initiator_host->GetProcess())
+ ->SimulateCrash();
+
+ EXPECT_FALSE(stream_info->DidExtensionStartNavigation());
+ EXPECT_FALSE(stream_info->extension_host_frame_tree_node_id());
+ EXPECT_FALSE(web_contents()->GetController().GetPendingEntry());
+}
+
// Verify `MimeHandlerStreamManager::PluginCanSave()` defaults to false
// and `MimeHandlerStreamManager::SetPluginCanSave()` updates the value.
TEST_F(MimeHandlerStreamManagerTest, PluginCanSave) {
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.
References
On This Page