CVE-2026-10956
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Pchrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc |
modified | |
whilechrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc |
modified | |
forextensions/renderer/guest_view/mime_handler_view/post_message_support.cc |
modified | |
ifextensions/renderer/guest_view/mime_handler_view/post_message_support.cc |
modified |
Files Changed
chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.ccextensions/renderer/guest_view/mime_handler_view/post_message_support.cc
Patch
From c8fcd16cc064a51bf8313609c9ec7088e0ef7019 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
Date: Tue, 28 Apr 2026 13:48:47 -0700
Subject: [PATCH] [MimeHandlerView] Fix Use-After-Free in PostMessageSupport::SetActive
PostMessageSupport::SetActive() flushes pending messages by iterating
over a member vector. Malicious JavaScript execution during this flush
(e.g., via structured clone getters) could synchronously detach the
frame and delete the PostMessageSupport instance, leading to a
Use-After-Free during iteration.
This fix moves the pending messages to a local stack variable using
std::exchange and uses a base::WeakPtr to safely detect if the object
has been synchronously destroyed during iteration.
Fixed: 506375731
Change-Id: I2ac433433b65ec197bfd1e99f6c81bb13de2c732
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7799636
Reviewed-by: Kevin McNee <mcnee@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1622014}
---
diff --git a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
index cdd5dee..8ed4e6a 100644
--- a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
+++ b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
@@ -747,3 +747,70 @@
EXPECT_EQ(expected_outermost_rfh,
guest_main_frame->GetOutermostMainFrameOrEmbedder());
}
+
+// Regression test for crbug.com/506375731.
+// PostMessageSupport::SetActive() would flush pending messages by iterating
+// over a member vector. Malicious JavaScript execution during this flush could
+// synchronously detach the frame and delete the PostMessageSupport instance,
+// leading to a Use-After-Free during iteration.
+IN_PROC_BROWSER_TEST_P(ChromeMimeHandlerViewTest, SetActiveUAF) {
+ TestGuestViewManager* manager = GetGuestViewManager();
+ TestMimeHandlerViewGuest::RegisterTestGuestViewType(manager);
+ ASSERT_TRUE(LoadTestExtension());
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/title1.html")));
+
+ auto* web_contents = GetEmbedderWebContents();
+
+ // 1. Create a "keeper" embed to keep MimeHandlerViewContainerManager alive.
+ // 2. Create a "victim" embed.
+ // 3. Queue messages with a malicious getter that detaches the victim.
+ // 4. When SetActive() flushes the queue, the first message detaches the
+ // frame, which synchronously deletes the PostMessageSupport instance.
+ // Subsequent messages would then access freed memory without the fix.
+ // We keep 'e' in window.e to avoid immediate destruction and potential
+ // DCHECKs in ~HTMLPlugInElement during the test, focusing on the UAF in
+ // PostMessageSupport.
+ const char kReproScript[] = R"(
+ (async () => {
+ const keeper = document.createElement('embed');
+ keeper.type = 'text/csv';
+ keeper.src = 'testEmbedded.csv';
+ document.body.appendChild(keeper);
+
+ const e = document.createElement('embed');
+ window.e = e;
+ e.type = 'text/csv';
+ e.src = 'testEmbedded.csv';
+ document.body.appendChild(e);
+
+ while (typeof e.postMessage !== 'function') {
+ await new Promise(r => requestAnimationFrame(r));
+ }
+
+ e.postMessage({
+ get a() {
+ window.e.remove();
+ return 1;
+ }
+ });
+ e.postMessage(1);
+ e.postMessage(2);
+ e.postMessage(3);
+ })();
+ )";
+
+ ASSERT_TRUE(content::ExecJs(web_contents, kReproScript));
+
+ // We expect at least the keeper guest view to be created and loaded.
+ guest_view::GuestViewBase* keeper_guest = manager->WaitForNextGuestViewCreated();
+ ASSERT_TRUE(keeper_guest);
+ ASSERT_TRUE(manager->WaitUntilAttachedAndLoaded(keeper_guest));
+
+ // The victim guest's creation and SetActive() call happen asynchronously.
+ // If the fix for crbug.com/506375731 is missing, the renderer will crash
+ // with a Use-After-Free during the message flush. If fixed, it returns
+ // safely.
+ ASSERT_TRUE(content::ExecJs(web_contents, "true"));
+}
diff --git a/extensions/renderer/guest_view/mime_handler_view/post_message_support.cc b/extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
index d14bb35..869bfb90 100644
--- a/extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
+++ b/extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
@@ -4,6 +4,8 @@
#include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h"
+#include <utility>
+
#include "base/metrics/histogram_functions.h"
#include "content/public/renderer/render_frame.h"
#include "content/public/renderer/v8_value_converter.h"
@@ -181,12 +183,22 @@
v8::Isolate* isolate = source->GetAgentGroupScheduler()->Isolate();
v8::HandleScope handle_scope(isolate);
v8::Context::Scope context_scope(source->MainWorldScriptContext());
- for (const auto& pending_message : pending_messages_) {
+
+ // PostJavaScriptMessage() runs script (structured clone of attacker-supplied
+ // values) and may re-enter delegate_->GetTargetFrame(), either of which can
+ // synchronously delete |this| and free |pending_messages_|'s backing buffer.
+ // Move the queue onto the stack and bail out if |this| goes away.
+ // See crbug.com/506375731.
+ auto weak_this = weak_factory_.GetWeakPtr();
+ std::vector<v8::Global<v8::Value>> messages =
+ std::exchange(pending_messages_, {});
+ for (const auto& pending_message : messages) {
PostJavaScriptMessage(isolate,
v8::Local<v8::Value>::New(isolate, pending_message));
+ if (!weak_this) {
+ return;
+ }
}
-
- pending_messages_.clear();
}
} // namespace extensions
Regression Test / PoC
diff --git a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
index cdd5dee..8ed4e6a 100644
--- a/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
+++ b/chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
@@ -747,3 +747,70 @@
EXPECT_EQ(expected_outermost_rfh,
guest_main_frame->GetOutermostMainFrameOrEmbedder());
}
+
+// Regression test for crbug.com/506375731.
+// PostMessageSupport::SetActive() would flush pending messages by iterating
+// over a member vector. Malicious JavaScript execution during this flush could
+// synchronously detach the frame and delete the PostMessageSupport instance,
+// leading to a Use-After-Free during iteration.
+IN_PROC_BROWSER_TEST_P(ChromeMimeHandlerViewTest, SetActiveUAF) {
+ TestGuestViewManager* manager = GetGuestViewManager();
+ TestMimeHandlerViewGuest::RegisterTestGuestViewType(manager);
+ ASSERT_TRUE(LoadTestExtension());
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/title1.html")));
+
+ auto* web_contents = GetEmbedderWebContents();
+
+ // 1. Create a "keeper" embed to keep MimeHandlerViewContainerManager alive.
+ // 2. Create a "victim" embed.
+ // 3. Queue messages with a malicious getter that detaches the victim.
+ // 4. When SetActive() flushes the queue, the first message detaches the
+ // frame, which synchronously deletes the PostMessageSupport instance.
+ // Subsequent messages would then access freed memory without the fix.
+ // We keep 'e' in window.e to avoid immediate destruction and potential
+ // DCHECKs in ~HTMLPlugInElement during the test, focusing on the UAF in
+ // PostMessageSupport.
+ const char kReproScript[] = R"(
+ (async () => {
+ const keeper = document.createElement('embed');
+ keeper.type = 'text/csv';
+ keeper.src = 'testEmbedded.csv';
+ document.body.appendChild(keeper);
+
+ const e = document.createElement('embed');
+ window.e = e;
+ e.type = 'text/csv';
+ e.src = 'testEmbedded.csv';
+ document.body.appendChild(e);
+
+ while (typeof e.postMessage !== 'function') {
+ await new Promise(r => requestAnimationFrame(r));
+ }
+
+ e.postMessage({
+ get a() {
+ window.e.remove();
+ return 1;
+ }
+ });
+ e.postMessage(1);
+ e.postMessage(2);
+ e.postMessage(3);
+ })();
+ )";
+
+ ASSERT_TRUE(content::ExecJs(web_contents, kReproScript));
+
+ // We expect at least the keeper guest view to be created and loaded.
+ guest_view::GuestViewBase* keeper_guest = manager->WaitForNextGuestViewCreated();
+ ASSERT_TRUE(keeper_guest);
+ ASSERT_TRUE(manager->WaitUntilAttachedAndLoaded(keeper_guest));
+
+ // The victim guest's creation and SetActive() call happen asynchronously.
+ // If the fix for crbug.com/506375731 is missing, the renderer will crash
+ // with a Use-After-Free during the message flush. If fixed, it returns
+ // safely.
+ ASSERT_TRUE(content::ExecJs(web_contents, "true"));
+}
Original Bug Report
Renderer UAF in PostMessageSupport::SetActive via structured-clone getters
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential use-after-free vulnerability exists in the renderer process within PostMessageSupport::SetActive. Synchronous JavaScript execution during the flushing of pending messages can lead to the destruction of the PostMessageSupport instance while its member vector is still being iterated.
Affected files:
extensions/renderer/guest_view/mime_handler_view/post_message_support.ccextensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.ccextensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc
Estimated timestamp from git blame: 2025-09-19
Summary
A potential Use-After-Free (UAF) vulnerability exists in extensions/renderer/guest_view/mime_handler_view/post_message_support.cc. The function PostMessageSupport::SetActive() flushes a queue of pending messages by iterating over the pending_messages_ vector and calling PostJavaScriptMessage() for each entry. Because PostJavaScriptMessage() eventually calls the target window’s postMessage which triggers synchronous JavaScript execution (via structured cloning of the message payload), a malicious getter can detach the underlying frame.
This detachment causes the MimeHandlerViewFrameContainer (the delegate) to synchronously delete the PostMessageSupport instance. Upon return from the JavaScript execution, SetActive() continues to iterate over the now-freed memory and uses a dangling this pointer.
Exploitation Path
- The attacker embeds a
MimeHandlerView-handled type (like a PDF) using a plugin element (e.g.,<embed>) and queues multiple messages viaplugin.postMessage()before the guest has finished loading. - The payloads contain malicious getters.
- When the guest loads, the browser sends a
DidLoadIPC, triggeringSetActive(). SetActive()iterates over the queued messages and callsPostJavaScriptMessage(). This calls the guest window’spostMessage, which invokes V8’s structured clone algorithm, executing the attacker’s first getter synchronously.- The getter calls
plugin.remove()to detach the frame, and makes a re-entrantpostMessagecall. - The re-entrant
postMessagecall eventually invokesMimeHandlerViewFrameContainer::AreFramesAlive(), which detects the frame detachment and callscontainer_manager_->RemoveFrameContainer(this). This synchronously destroys the delegate and thePostMessageSupportobject. - The first getter completes, and the second getter is invoked. The second getter executes a heap spray to reclaim the memory previously occupied by the
PostMessageSupportobject and itsstd::vectorbacking store. - When the JavaScript execution finishes,
SetActive()resumes iteration. The stack iterators point to the reclaimed backing store, and the implicitthispointer points to the reclaimedPostMessageSupportobject. - If the attacker sprays the
is_active_flag asfalse, the subsequent call toPostJavaScriptMessage()will executepending_messages_.push_back(...). Operating on attacker-controlledstd::vectorinternal pointers, this yields a reliable arbitrary write primitive.
Suggested Fix
PostMessageSupport::SetActive() should be protected against re-entrancy and synchronous deletion. One approach is to use a base::WeakPtr to check if the object has been destroyed during iteration, or move the pending_messages_ into a local variable before iterating:
void PostMessageSupport::SetActive() {
// ...
std::vector<v8::Global<v8::Value>> messages_to_send = std::move(pending_messages_);
base::WeakPtr<PostMessageSupport> weak_this = weak_factory_.GetWeakPtr();
for (const auto& pending_message : messages_to_send) {
PostJavaScriptMessage(isolate,
v8::Local<v8::Value>::New(isolate, pending_message));
if (!weak_this) {
return;
}
}
}
Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f
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.