Critical chrome UAF 🔧 Commit mapped

Overview

Critical
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in PDF
DescriptionUse after free in PDF
ComponentPDF
Sub-componentMime_handler
Bug ClassUAF
Tracker568719364
Fix commitcb21df9dd4c5 (chromium/src) +52/-2
CISA KEVNot listed
CreditedXinyang Ge
Disclosed2026-10-13

Changed Functions

FunctionChangeNotes
TEST_F
extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
modified

Files Changed

  • extensions/browser/mime_handler/mime_handler_stream_manager.cc
  • chrome/browser/pdf/mime_handler_stream_manager_pdf_unittest.cc
  • extensions/browser/mime_handler/mime_handler_stream_manager_unittest.cc
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.