Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Blink
DescriptionUse after free in Blink
ComponentBlink
Bug ClassUAF
Tracker523292588
Fix commit9fb841bf28c3 (chromium/src) +13/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-23

Changed Functions

FunctionChangeNotes
if
third_party/blink/renderer/platform/widget/widget_base.cc
modified

Files Changed

  • third_party/blink/renderer/core/editing/frame_selection.cc
  • third_party/blink/renderer/platform/widget/widget_base.cc
From 9fb841bf28c373fccf20a546545ed581e45031d7 Mon Sep 17 00:00:00 2001
From: Koji Ishii <[email protected]>
Date: Fri, 17 Jul 2026 06:41:19 -0700
Subject: [PATCH] Add validity checks after `CompositionRange()`

Following up crrev.com/c/7840643, this patch adds validity
checks after calls to `CompositionRange()`, which invokes
`Document::UpdateStyleAndLayout()`.

Also change `FrameSelection::RootEditableElementOrDocumentElement()`
to return `nullptr` when the selection is no longer available
after the layout it forces, instead of dereferencing a cleared
`document_`.

Bug: 523292588
Change-Id: I4a82a3b57fb00137dd45be946fd8e045ed46be2f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8106916
Reviewed-by: Dave Tapuska <[email protected]>
Auto-Submit: Koji Ishii <[email protected]>
Commit-Queue: Dave Tapuska <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1663857}
---

diff --git a/third_party/blink/renderer/core/editing/frame_selection.cc b/third_party/blink/renderer/core/editing/frame_selection.cc
index 175c5a8a..96220191 100644
--- a/third_party/blink/renderer/core/editing/frame_selection.cc
+++ b/third_party/blink/renderer/core/editing/frame_selection.cc
@@ -152,9 +152,15 @@
 }
 
 Element* FrameSelection::RootEditableElementOrDocumentElement() const {
+  if (!IsAvailable()) {
+    return nullptr;
+  }
   // TODO(editing-dev): The use of UpdateStyleAndLayout
   // needs to be audited.  See http://crbug.com/590369 for more details.
   GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kSelection);
+  if (!IsAvailable()) {
+    return nullptr;
+  }
 
   Element* selection_root =
       ComputeVisibleSelectionInDomTree().RootEditableElement();
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index fd4d3bb5..3023c5c9 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -1449,8 +1449,15 @@
     // Composition information is only available on editable node.
     range = gfx::Range::InvalidRange();
   } else {
+    base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
     GetCompositionRange(&range);
+    if (!weak_this) {
+      return;
+    }
     GetCompositionCharacterBounds(&character_bounds);
+    if (!weak_this) {
+      return;
+    }
   }
 
   if (!immediate_request &&
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free in WidgetBase::UpdateCompositionInfo due to synchronous layout

Flapjack, 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) exists in WidgetBase::UpdateCompositionInfo when calculating composition ranges. Forcing a synchronous layout update can trigger JavaScript execution via plugin disposal, allowing an attacker to detach the iframe and destroy the WidgetBase object while it is still actively used on the call stack. This results in a dangling this pointer, which can potentially be exploited for Remote Code Execution in the renderer process.

Affected files:

  • third_party/blink/renderer/platform/widget/widget_base.cc
  • third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
  • third_party/blink/renderer/core/exported/web_input_method_controller_impl.cc
  • third_party/blink/renderer/core/frame/web_local_frame_impl.cc

Estimated timestamp from git blame: 2020-06-26

Description

A potential Use-After-Free (UAF) vulnerability has been identified in WidgetBase::UpdateCompositionInfo within the Blink renderer. The issue arises because calculating IME composition bounds can force synchronous layout updates, which in turn can execute arbitrary JavaScript. If the JavaScript destroys the iframe containing the WidgetBase, the object is deleted while its this pointer is still actively used on the call stack, leading to a UAF.

Execution Path

The potential vulnerability follows this sequence:

  1. WidgetBase::UpdateCompositionInfo is triggered either by a Mojo IPC (RequestCompositionUpdates) or asynchronously by the compositor during WillBeginMainFrame (via UpdateSelectionBounds). Crucially, these paths do not employ an ImeEventGuard to detect object destruction.
  2. The method calls GetCompositionRange(&range), which delegates through WebFrameWidgetImpl to WebInputMethodControllerImpl::CompositionRange.
  3. To accurately determine the composition range of an editable element, the input method controller forces a layout update by calling editable->GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kInput).
  4. During UpdateStyleAndLayout, a HTMLFrameOwnerElement::PluginDisposeSuspendScope is instantiated to defer plugin destruction until layout completes.
  5. If a plugin element (e.g., <object> or <embed>) is marked for disposal during this layout phase (e.g., by matching CSS that sets display: none), the ~PluginDisposeSuspendScope destructor will invoke WebPluginContainerImpl::Dispose.
  6. WebPluginContainerImpl::Dispose explicitly permits synchronous JavaScript execution by instantiating ScriptForbiddenScope::AllowUserAgentScript before destroying the plugin.
  7. An attacker’s JavaScript callback executes synchronously and detaches the iframe from the DOM (e.g., frameElement.remove()).
  8. Detaching the iframe synchronously invokes WebFrameWidgetImpl::Close, which calls widget_base_.reset(), instantly freeing the memory backing the WidgetBase object.
  9. Execution unwinds back to WidgetBase::UpdateCompositionInfo. The implicitly used this pointer is now dangling.
  10. The method proceeds to call GetCompositionCharacterBounds(&character_bounds). This accesses the client_ member variable from the freed object and attempts a virtual function call (client_->FrameWidget()).

Exploitability

MiraclePtr (BackupRefPtr) does not protect this access. The WidgetBase instance is owned by a std::unique_ptr, meaning its memory is immediately returned to PartitionAlloc. Furthermore, the vulnerability relies on the raw this pointer on the call stack. An attacker can potentially heap-spray the renderer process during the synchronous JavaScript callback to reclaim the freed memory. By overwriting the client_ pointer and controlling its vtable, the attacker could hijack the subsequent virtual function call, achieving Remote Code Execution (RCE) within the sandboxed renderer.

Note: These steps represent a potential attack path derived from static analysis; our tooling agent does not currently have the capability to execute a working Proof of Concept.

Suggested Fix

To remediate this issue, execution paths that trigger UpdateCompositionInfo should be protected against object destruction.

This can be achieved by checking a base::WeakPtr<WidgetBase> before and after any calls that might synchronously trigger layout or script execution (like GetCompositionRange). Alternatively, wrapping the logic inside UpdateCompositionInfo with an ImeEventGuard (if appropriate for the context) would safely detect if the object has been destroyed and safely bail out.

Evaluated with Chrome root at commit: 2155cb00003ec35716a76ed3246eae995f87b7ff


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