Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in MimeHandlerView
DescriptionUse after free in MimeHandlerView
ComponentMimeHandlerView
Bug ClassUAF
Tracker506375731
Fix commitc8fcd16cc064 (chromium/src) +82/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_P
chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
modified
while
chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
modified
for
extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
modified
if
extensions/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.cc
  • extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
From c8fcd16cc064a51bf8313609c9ec7088e0ef7019 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
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 <[email protected]>
Commit-Queue: Andrew Paseltiner <[email protected]>
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
Loading diff…

Regression Test / PoC

shipped with the fix
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"));
+}
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • extensions/renderer/guest_view/mime_handler_view/mime_handler_view_frame_container.cc
  • extensions/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

  1. The attacker embeds a MimeHandlerView-handled type (like a PDF) using a plugin element (e.g., <embed>) and queues multiple messages via plugin.postMessage() before the guest has finished loading.
  2. The payloads contain malicious getters.
  3. When the guest loads, the browser sends a DidLoad IPC, triggering SetActive().
  4. SetActive() iterates over the queued messages and calls PostJavaScriptMessage(). This calls the guest window’s postMessage, which invokes V8’s structured clone algorithm, executing the attacker’s first getter synchronously.
  5. The getter calls plugin.remove() to detach the frame, and makes a re-entrant postMessage call.
  6. The re-entrant postMessage call eventually invokes MimeHandlerViewFrameContainer::AreFramesAlive(), which detects the frame detachment and calls container_manager_->RemoveFrameContainer(this). This synchronously destroys the delegate and the PostMessageSupport object.
  7. 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 PostMessageSupport object and its std::vector backing store.
  8. When the JavaScript execution finishes, SetActive() resumes iteration. The stack iterators point to the reclaimed backing store, and the implicit this pointer points to the reclaimed PostMessageSupport object.
  9. If the attacker sprays the is_active_ flag as false, the subsequent call to PostJavaScriptMessage() will execute pending_messages_.push_back(...). Operating on attacker-controlled std::vector internal 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.

View on issue tracker