Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Platform
DescriptionUse after free in Platform
ComponentPlatform
Sub-componentPlatform
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIPlausible
Reviewer notes: Claim that GetTextInputType 'executes script' to destroy the widget is asserted, not shown; the test only uses a mock client calling Shutdown. Concrete web trigger for reentrant destruction is undemonstrated; otherwise matches diff.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker553129513
Fix commit349cc893f9ec (chromium/src) +22/-3
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-08-26 (per vendor advisory)
Fix landed2026-08-27 (public git)
Disclosed2026-09-22
Patch gap26 days — fix public in source before the advisory

Background

`WidgetBase`
The Blink renderer-side object (third_party/blink/renderer/platform/widget/widget_base.cc) that backs a widget and mediates input, IME, and text-input state with the browser process.
`GetTextInputType()`
A call routed through the WidgetBaseClient that queries the current editable node’s input type and can synchronously run script/layout, which in turn may tear down the owning WidgetBase.
`base::WeakPtr<WidgetBase>`
A weak reference obtained from weak_ptr_factory_ that becomes null once the WidgetBase is destroyed, used as a liveness sentinel after a potentially reentrant call.
IME/composition update
The text-input reporting path (UpdateTextInputState, UpdateCompositionInfo) that gathers input type, selection, and composition data and sends it to the browser, running while the widget must remain alive.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: Claim that GetTextInputType 'executes script' to destroy the widget is asserted, not shown; the test only uses a mock client calling Shutdown. Concrete web trigger for reentrant destruction is undemonstrated; otherwise matches diff.

Both WidgetBase::UpdateTextInputState and WidgetBase::UpdateCompositionInfo call GetTextInputType() and then continue to dereference this (reading frame_widget, calling GetCompositionRange, touching member state) under the assumption that the WidgetBase is still alive. However, GetTextInputType() dispatches into client code that can execute script and synchronously destroy the WidgetBase, violating the invariant that the object outlives the method activation. When that happens, the remainder of the method operates on a freed WidgetBase, producing a use-after-free.

The fix captures weak_ptr_factory_.GetWeakPtr() immediately before calling GetTextInputType() and returns early if the WeakPtr is null afterward, so no freed-object access occurs; in UpdateTextInputState an existing later WeakPtr capture is hoisted above the GetTextInputType() call, and in UpdateCompositionInfo the result is cached in text_input_type before the liveness check.

Key insight
The single mistake is treating GetTextInputType() as a pure query when it can reenter and destroy the very WidgetBase executing it; the fix converts each call site into a guarded reentrancy point by checking a base::WeakPtr<WidgetBase> before any further member access.

Attack Path

  1. Reach the text-input reporting path Content in a renderer causes an IME/text-input state refresh, driving WidgetBase::UpdateTextInputState or WidgetBase::UpdateCompositionInfo to run.
  2. Trigger reentrancy via `GetTextInputType()` The GetTextInputType() call synchronously invokes client logic (script/layout) that, under attacker-influenced conditions, tears down the owning WidgetBase (the added test models this by calling Shutdown(false) and resetting the widget from within the mocked GetTextInputType).
  3. Use the freed widget Before the fix, execution returns into the caller and keeps using this (e.g. frame_widget, GetCompositionRange, member reads), dereferencing the freed WidgetBase. (inferred)
  4. Escalate the freed access Controlling the freed WidgetBase allocation to obtain a read/write primitive or corrupt object state for further exploitation. (inferred)

Impact Assessment

An attacker who can drive text-input/IME updates and cause reentrant destruction of the WidgetBase gains a use-after-free within the renderer process, requiring the ability to run content that synchronously destroys the widget during GetTextInputType(). Any escalation to a read/write primitive or code execution beyond the freed-object access is (inferred) and not demonstrated by the patch. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8.

Changed Functions

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

Files Changed

  • third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
  • third_party/blink/renderer/platform/widget/widget_base.cc

Audit Directions

  • Reentrant client callbacks
    Audit other WidgetBaseClient/frame-widget callbacks (input type, selection, composition, layout-triggering queries) whose callers keep dereferencing this afterward without a WeakPtr recheck.
  • Post-call member access
    Look for methods that call a virtual/dispatched function and then read members or call further methods on the same object, and confirm a liveness sentinel guards each such continuation.
  • Hoisted vs. missing guards
    Review paths where a WeakPtr check already exists after one reentrant call but earlier reentrant calls in the same method (like GetTextInputType()) remain unguarded.
From 349cc893f9ec6582f528e79a35ccbce803ffba13 Mon Sep 17 00:00:00 2001
From: Dave Tapuska <[email protected]>
Date: Thu, 27 Aug 2026 09:49:18 -0700
Subject: [PATCH] Prevent crash if WidgetBase is destroyed during GetTextInputType.

During UpdateTextInputState and composition updates, calling
GetTextInputType can result in the destruction of the WidgetBase. This
CL adds WeakPtr checks immediately after GetTextInputType calls to
ensure the widget is still alive before proceeding, preventing potential
use-after-free crashes. A unit test is also added to verify this
behavior.

BUG=553129513

Change-Id: Id77925e08581ef1b1d50f3b8404466e04c083ff3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8299776
Commit-Queue: Dave Tapuska <[email protected]>
Reviewed-by: Vladimir Levin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1687328}
---
diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc
index 1d62e67..9c3b186d 100644
--- a/third_party/blink/renderer/platform/widget/widget_base.cc
+++ b/third_party/blink/renderer/platform/widget/widget_base.cc
@@ -1262,7 +1262,11 @@
       ime_event_guard_->set_show_virtual_keyboard(true);
     return;
   }
+  base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
   ui::TextInputType new_type = GetTextInputType();
+  if (!weak_this) {
+    return;
+  }
   if (IsDateTimeInput(new_type))
     return;  // Not considered as a text input field in WebKit/Chromium.
 
@@ -1274,7 +1278,6 @@
   std::optional<gfx::Rect> control_bounds;
   std::optional<gfx::Rect> selection_bounds;
   if (frame_widget) {
-    base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
     new_info = frame_widget->TextInputInfo();
     if (!weak_this) {
       return;
@@ -1457,11 +1460,16 @@
   gfx::Range range;
   Vector<gfx::Rect> character_bounds;
 
-  if (GetTextInputType() == ui::TextInputType::TEXT_INPUT_TYPE_NONE) {
+  base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
+  ui::TextInputType text_input_type = GetTextInputType();
+  if (!weak_this) {
+    return;
+  }
+
+  if (text_input_type == ui::TextInputType::TEXT_INPUT_TYPE_NONE) {
     // 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;
diff --git a/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc b/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
index 046b4e6..ff298b13 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
@@ -36,6 +36,7 @@
               (const WebGestureEvent&, bool*),
               (override));
   MOCK_METHOD(void, WillHandleMouseEvent, (const WebMouseEvent&), (override));
+  MOCK_METHOD(WebTextInputType, GetTextInputType, (), (override));
   const display::ScreenInfos& GetOriginalScreenInfos() override {
     return screen_infos_;
   }
@@ -159,4 +160,14 @@
   EXPECT_TRUE(callback_run);
 }
 
+TEST_F(WidgetBaseInputHandlerTest, UpdateTextInputStateDestroysWidget) {
+  EXPECT_CALL(client_, GetTextInputType()).WillOnce([&]() {
+    widget_base_->Shutdown(false);
+    widget_base_.reset();
+    return WebTextInputType::kWebTextInputTypeText;
+  });
+
+  widget_base_->UpdateTextInputState();
+}
+
 }  // namespace blink
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc b/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
index 046b4e6..ff298b13 100644
--- a/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
+++ b/third_party/blink/renderer/platform/widget/input/widget_base_input_handler_unittest.cc
@@ -36,6 +36,7 @@
               (const WebGestureEvent&, bool*),
               (override));
   MOCK_METHOD(void, WillHandleMouseEvent, (const WebMouseEvent&), (override));
+  MOCK_METHOD(WebTextInputType, GetTextInputType, (), (override));
   const display::ScreenInfos& GetOriginalScreenInfos() override {
     return screen_infos_;
   }
@@ -159,4 +160,14 @@
   EXPECT_TRUE(callback_run);
 }
 
+TEST_F(WidgetBaseInputHandlerTest, UpdateTextInputStateDestroysWidget) {
+  EXPECT_CALL(client_, GetTextInputType()).WillOnce([&]() {
+    widget_base_->Shutdown(false);
+    widget_base_.reset();
+    return WebTextInputType::kWebTextInputTypeText;
+  });
+
+  widget_base_->UpdateTextInputState();
+}
+
 }  // namespace blink
Loading diff…

Original Bug Report

reported by [email protected]

UAF in WidgetBase::UpdateTextInputStateInternal via GetTextInputType()

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: In WidgetBase::UpdateTextInputStateInternal, calling GetTextInputType() triggers a style and layout update via InputMethodController::TextInputType(). If this layout update causes synchronous frame detachment or widget destruction, execution continues on the freed WidgetBase instance and calls client_->FrameWidget() before the weak pointer check at line 1277. This leads to a potential use-after-free and virtual call dispatch through freed memory in the renderer process.

Affected files:

  • third_party/blink/renderer/platform/widget/widget_base.cc

Estimated timestamp from git blame: 2020-06-19

Summary

In third_party/blink/renderer/platform/widget/widget_base.cc, WidgetBase::UpdateTextInputStateInternal invokes GetTextInputType() at line 1265, which can trigger a layout and style tree update (Document::UpdateStyleAndLayoutTree). If synchronous script execution or frame detachment occurs during this update and deletes the WidgetBase instance, UpdateTextInputStateInternal continues execution and accesses this->client_ at line 1269 before taking the weak_this liveness check at line 1277.

Root Cause Analysis

In third_party/blink/renderer/platform/widget/widget_base.cc:1256-1281:

void WidgetBase::UpdateTextInputStateInternal(bool show_virtual_keyboard,
                                              bool reply_to_request) {
  TRACE_EVENT0("renderer", "WidgetBase::UpdateTextInputStateInternal");
  if (ime_event_guard_) {
    DCHECK(!reply_to_request);
    if (show_virtual_keyboard)
      ime_event_guard_->set_show_virtual_keyboard(true);
    return;
  }
  ui::TextInputType new_type = GetTextInputType();   // [1] Calls layout-forcing method
  if (IsDateTimeInput(new_type))
    return;

  FrameWidget* frame_widget = client_->FrameWidget(); // [2] UAF: reads this->client_ on freed object

  blink::WebTextInputInfo new_info;
  ...
  if (frame_widget) {
    base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr(); // [3] Guard placed too late
    new_info = frame_widget->TextInputInfo();
    if (!weak_this) {
      return;
    }
  1. At [1] (widget_base.cc:1265), WidgetBase::GetTextInputType() forwards through client_->GetTextInputType() (web_frame_widget_impl.cc:3334) to InputMethodController::TextInputType() (third_party/blink/renderer/core/editing/ime/input_method_controller.cc:1976-2049). For focused editable elements (contenteditable), line 2044 explicitly executes GetDocument().UpdateStyleAndLayoutTree().
  2. In Document::UpdateStyleAndLayoutTree (document.cc:2630-2670), PluginDisposeSuspendScope and ScriptForbiddenScope are used. When destructed at function exit, ~ScriptForbiddenScope runs before ~PluginDisposeSuspendScope, and deferred plugin disposals are invoked under ScriptForbiddenScope::AllowUserAgentScript (web_plugin_container_impl.cc:871).
  3. If script execution or DOM tree mutations during this phase detach the local root frame or its parent (RemoteFrame::DetachChildren / LocalFrame::DetachImpl), WebFrameWidgetImpl::Close (web_frame_widget_impl.cc:538-580) calls widget_base_->Shutdown() and synchronously deletes the widget via widget_base_.reset() at line 574.
  4. Because the calling function holds this as a raw stack pointer and no surviving raw_ptr<WidgetBase> holds a BackupRefPtr reference, WidgetBase is freed into PartitionAlloc immediately.
  5. Control returns from GetTextInputType() to line 1269, where client_->FrameWidget() reads this->client_ from the freed memory chunk and executes a virtual method dispatch. The weak_this liveness check at line 1277 is reached only after this virtual call.

Potential Trigger Steps

(Note: These are suggested theoretical steps as our analysis tooling does not have runtime execution capabilities.)

  1. Setup an outer page $A$ (Origin $X$) embedding a cross-origin iframe $B$ (Origin $Y$, remote in process $P$), which in turn embeds an iframe $C$ (Origin $X$, local root subframe in process $P$).
  2. In frame $C$, create a contenteditable element and a plugin element (<embed>/<object>).
  3. On user click or tap in frame $C$, handle the input event by focusing the contenteditable element and setting the plugin element to display: none.
  4. When input processing triggers WidgetBase::ShowVirtualKeyboard() (widget_base_input_handler.cc:465) -> UpdateTextInputStateInternal(true, false):
    • GetTextInputType() at line 1265 forces UpdateStyleAndLayoutTree(), detaching the plugin’s layout object and queuing it for deferred disposal.
    • At scope exit, WebPluginContainerImpl::Dispose() calls Destroy().
    • The plugin teardown script removes iframe $B$ via window.top.document.getElementById('iframeB').remove().
    • Detaching $B$ synchronously detaches $C$, executing WebFrameWidgetImpl::Close -> widget_base_.reset().
  5. When GetTextInputType() returns, line 1269 reads this->client_ and invokes virtual method FrameWidget() on the freed/reclaimed memory.

Suggested Fix

Capture a WeakPtr<WidgetBase> at the beginning of UpdateTextInputStateInternal before calling GetTextInputType(), and return immediately if the widget was destroyed:

void WidgetBase::UpdateTextInputStateInternal(bool show_virtual_keyboard,
                                              bool reply_to_request) {
  TRACE_EVENT0("renderer", "WidgetBase::UpdateTextInputStateInternal");
  if (ime_event_guard_) {
    DCHECK(!reply_to_request);
    if (show_virtual_keyboard)
      ime_event_guard_->set_show_virtual_keyboard(true);
    return;
  }
  base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
  ui::TextInputType new_type = GetTextInputType();
  if (!weak_this || IsDateTimeInput(new_type))
    return;

  FrameWidget* frame_widget = client_->FrameWidget();
  ...
  if (frame_widget) {
    new_info = frame_widget->TextInputInfo();
    if (!weak_this) {
      return;
    }

Evaluated with Chrome root at commit: c6718ebbf2c677c6f63691e28c7801c563f6b19c


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