Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Guest View
DescriptionUse after free in Guest View
ComponentGuest View
Bug ClassUAF
Tracker516910450
Fix commit6787a994f7a9 (chromium/src) +66/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_P
chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.cc
modified
get
chrome/browser/guest_view/mime_handler_view/chrome_mime_handler_view_browsertest.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 6787a994f7a92c0b50a91cbe8947a88f9ca9b431 Mon Sep 17 00:00:00 2001
From: Kevin McNee <mcnee@chromium.org>
Date: Thu, 28 May 2026 09:51:01 -0700
Subject: [PATCH] MimeHandlerView: Guard against malicious script execution during post message

When looking up the postMessage function for the inner embed, a
malicious script could execute and destroy the parent frame. We now use
a weak ptr so that we do not proceed if the object has been
synchronously destroyed.

Fixed: 516910450
Change-Id: Ide9d209f0a5399920c7f3d9a8d631947ed68040a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7880786
Reviewed-by: Andy Phan <andyphan@chromium.org>
Commit-Queue: Andy Phan <andyphan@chromium.org>
Auto-Submit: Kevin McNee <mcnee@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1637767}
---

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 8ed4e6a..30555ba 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
@@ -814,3 +814,63 @@
   // safely.
   ASSERT_TRUE(content::ExecJs(web_contents, "true"));
 }
+
+IN_PROC_BROWSER_TEST_P(ChromeMimeHandlerViewTest, PostMessageGetterOverwrite) {
+  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();
+
+  const char kSetupScript[] = R"(
+    (() => {
+      const iframe = document.createElement('iframe');
+      iframe.src = 'testEmbedded.csv';
+      document.body.appendChild(iframe);
+    })();
+  )";
+
+  ASSERT_TRUE(content::ExecJs(web_contents, kSetupScript));
+
+  guest_view::GuestViewBase* guest = manager->WaitForNextGuestViewCreated();
+  ASSERT_TRUE(guest);
+  ASSERT_TRUE(manager->WaitUntilAttachedAndLoaded(guest));
+
+  // This replaces the internal embed element with our own frame of the same
+  // name. The new frame has a custom getter for the postMessage property, which
+  // destroys the parent iframe when accessed. Then we trigger a postMessage.
+  // The implementation should be robust to this manipulation.
+  // See https://crbug.com/516910450
+  const char kReproScript[] = R"(
+    (async () => {
+      const iframe = document.querySelector('iframe');
+      while (typeof iframe.contentDocument.querySelector('embed')?.postMessage
+                 !== 'function') {
+        await new Promise(r => setTimeout(r));
+      }
+      let innerEmbed = iframe.contentDocument.querySelector('embed');
+      let internalid = innerEmbed.getAttribute('internalid');
+      let savedPostMessage = innerEmbed.postMessage;
+
+      innerEmbed.remove();
+      let evilFrame = iframe.contentDocument.createElement('iframe');
+      evilFrame.name = internalid;
+      iframe.contentDocument.body.appendChild(evilFrame);
+
+      Object.defineProperty(evilFrame.contentWindow, 'postMessage', {
+        configurable: true,
+        get() {
+          iframe.remove();
+          return function() {};
+        }
+      });
+
+      savedPostMessage({});
+    })();
+  )";
+
+  ASSERT_TRUE(content::ExecJs(web_contents, kReproScript));
+}
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 869bfb90..0c17acc 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
@@ -147,10 +147,16 @@
   v8::Local<v8::Object> target_window_proxy =
       target_frame->GlobalProxy(isolate);
   gin::Dictionary window_object(isolate, target_window_proxy);
+  auto weak_this = weak_factory_.GetWeakPtr();
   v8::Local<v8::Function> post_message;
   if (!window_object.Get(std::string(kPostMessageName), &post_message)) {
     return;
   }
+  if (!weak_this) {
+    // Getting the function may have executed a malicious script that destroyed
+    // `this`. See https://crbug.com/516910450
+    return;
+  }
 
   v8::Local<v8::Value> args[] = {
       message,
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 8ed4e6a..30555ba 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
@@ -814,3 +814,63 @@
   // safely.
   ASSERT_TRUE(content::ExecJs(web_contents, "true"));
 }
+
+IN_PROC_BROWSER_TEST_P(ChromeMimeHandlerViewTest, PostMessageGetterOverwrite) {
+  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();
+
+  const char kSetupScript[] = R"(
+    (() => {
+      const iframe = document.createElement('iframe');
+      iframe.src = 'testEmbedded.csv';
+      document.body.appendChild(iframe);
+    })();
+  )";
+
+  ASSERT_TRUE(content::ExecJs(web_contents, kSetupScript));
+
+  guest_view::GuestViewBase* guest = manager->WaitForNextGuestViewCreated();
+  ASSERT_TRUE(guest);
+  ASSERT_TRUE(manager->WaitUntilAttachedAndLoaded(guest));
+
+  // This replaces the internal embed element with our own frame of the same
+  // name. The new frame has a custom getter for the postMessage property, which
+  // destroys the parent iframe when accessed. Then we trigger a postMessage.
+  // The implementation should be robust to this manipulation.
+  // See https://crbug.com/516910450
+  const char kReproScript[] = R"(
+    (async () => {
+      const iframe = document.querySelector('iframe');
+      while (typeof iframe.contentDocument.querySelector('embed')?.postMessage
+                 !== 'function') {
+        await new Promise(r => setTimeout(r));
+      }
+      let innerEmbed = iframe.contentDocument.querySelector('embed');
+      let internalid = innerEmbed.getAttribute('internalid');
+      let savedPostMessage = innerEmbed.postMessage;
+
+      innerEmbed.remove();
+      let evilFrame = iframe.contentDocument.createElement('iframe');
+      evilFrame.name = internalid;
+      iframe.contentDocument.body.appendChild(evilFrame);
+
+      Object.defineProperty(evilFrame.contentWindow, 'postMessage', {
+        configurable: true,
+        get() {
+          iframe.remove();
+          return function() {};
+        }
+      });
+
+      savedPostMessage({});
+    })();
+  )";
+
+  ASSERT_TRUE(content::ExecJs(web_contents, kReproScript));
+}
Loading diff…

Original Bug Report

reported by vm...@google.com

Use-After-Free in PostMessageSupport::PostJavaScriptMessage via synchronous getter

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. 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 (UAF) vulnerability exists in the MimeHandlerView implementation where retrieving the ‘postMessage’ property can trigger synchronous JavaScript execution. If the target frame is same-origin, an attacker-controlled getter can detach the frame, synchronously destroying the PostMessageSupport object. Subsequent execution continues and accesses the deleted ’this’ object, leading to a Use-After-Free.

Affected files:

  • extensions/renderer/guest_view/mime_handler_view/post_message_support.cc
  • extensions/renderer/guest_view/mime_handler_view/mime_handler_view_container_manager.cc

Estimated timestamp from git blame: 2019-04-23

Detailed Writeup

Vulnerability Analysis

In PostMessageSupport::PostJavaScriptMessage (located in extensions/renderer/guest_view/mime_handler_view/post_message_support.cc), the code performs a property lookup on the target window proxy to retrieve the postMessage function:

  gin::Dictionary window_object(isolate, target_window_proxy);
  v8::Local<v8::Function> post_message;
  if (!window_object.Get(std::string(kPostMessageName), &post_message)) {
    return;
  }

If the target window is same-origin and has a custom getter defined on the postMessage property, calling window_object.Get() will synchronously execute JavaScript. Inside this getter, the attacker can detach the frame (for instance, by removing the embedding element from the DOM).

Detaching the frame synchronously triggers the destruction of the underlying RenderFrameImpl, which destroys the MimeHandlerViewContainerManager and the associated PostMessageSupport instance.

Upon returning from the V8 property lookup, PostJavaScriptMessage continues execution:

  delegate_->GetSourceFrame()->CallFunctionEvenIfScriptDisabled(
      post_message.As<v8::Function>(), target_window_proxy, std::size(args),
      args);

Since this has already been deleted, dereferencing delegate_ (which is a member variable of PostMessageSupport) results in a Use-After-Free (UAF) on the this pointer.

Potential Trigger Scenario

An attacker could potentially trigger this by:

  1. Embedding a MimeHandlerView-supported document (such as a PDF).
  2. Caching the postMessage function of the plugin element.
  3. Setting up a same-origin target iframe named after the plugin’s internal ID, and defining a custom getter on its postMessage property.
  4. Invoking the cached postMessage function, forcing the property lookup on the custom getter.
  5. Synchronously detaching the frame during the getter invocation to destroy the PostMessageSupport instance before the lookup returns.

Note: These are potential steps based on source code analysis; our tools have not executed this code to verify a functional proof-of-concept.

Suggested Fix

To prevent this issue, verify that the PostMessageSupport instance is still alive after executing any operation that can run user-defined JavaScript. This can be achieved by checking a base::WeakPtr<PostMessageSupport> immediately after the property lookup.

  auto weak_this = weak_factory_.GetWeakPtr();
  v8::Local<v8::Function> post_message;
  if (!window_object.Get(std::string(kPostMessageName), &post_message)) {
    return;
  }
  if (!weak_this) {
    return;
  }

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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