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 <[email protected]> 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 <[email protected]> Commit-Queue: Andy Phan <[email protected]> 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