CVE-2026-95282
Overview
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
WidgetBaseClientthat queries the current editable node’s input type and can synchronously run script/layout, which in turn may tear down the owningWidgetBase. - `base::WeakPtr<WidgetBase>`
- A weak reference obtained from
weak_ptr_factory_that becomes null once theWidgetBaseis 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
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.
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
- Reach the text-input reporting path
Content in a renderer causes an IME/text-input state refresh, driving
WidgetBase::UpdateTextInputStateorWidgetBase::UpdateCompositionInfoto run. - Trigger reentrancy via `GetTextInputType()`
The
GetTextInputType()call synchronously invokes client logic (script/layout) that, under attacker-influenced conditions, tears down the owningWidgetBase(the added test models this by callingShutdown(false)and resetting the widget from within the mockedGetTextInputType). - 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 freedWidgetBase. (inferred) - Escalate the freed access
Controlling the freed
WidgetBaseallocation to obtain a read/write primitive or corrupt object state for further exploitation. (inferred)
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/platform/widget/widget_base.cc |
modified | |
TEST_Fthird_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.ccthird_party/blink/renderer/platform/widget/widget_base.cc
Audit Directions
- Reentrant client callbacksAudit other
WidgetBaseClient/frame-widget callbacks (input type, selection, composition, layout-triggering queries) whose callers keep dereferencingthisafterward without aWeakPtrrecheck. - Post-call member accessLook 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 guardsReview paths where a
WeakPtrcheck already exists after one reentrant call but earlier reentrant calls in the same method (likeGetTextInputType()) remain unguarded.
Patch
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
Regression Test / PoC
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
Original Bug Report
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;
}
- At
[1](widget_base.cc:1265),WidgetBase::GetTextInputType()forwards throughclient_->GetTextInputType()(web_frame_widget_impl.cc:3334) toInputMethodController::TextInputType()(third_party/blink/renderer/core/editing/ime/input_method_controller.cc:1976-2049). For focused editable elements (contenteditable), line 2044 explicitly executesGetDocument().UpdateStyleAndLayoutTree(). - In
Document::UpdateStyleAndLayoutTree(document.cc:2630-2670),PluginDisposeSuspendScopeandScriptForbiddenScopeare used. When destructed at function exit,~ScriptForbiddenScoperuns before~PluginDisposeSuspendScope, and deferred plugin disposals are invoked underScriptForbiddenScope::AllowUserAgentScript(web_plugin_container_impl.cc:871). - 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) callswidget_base_->Shutdown()and synchronously deletes the widget viawidget_base_.reset()at line 574. - Because the calling function holds
thisas a raw stack pointer and no survivingraw_ptr<WidgetBase>holds a BackupRefPtr reference,WidgetBaseis freed into PartitionAlloc immediately. - Control returns from
GetTextInputType()to line 1269, whereclient_->FrameWidget()readsthis->client_from the freed memory chunk and executes a virtual method dispatch. Theweak_thisliveness 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.)
- 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$).
- In frame $C$, create a contenteditable element and a plugin element (
<embed>/<object>). - 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. - When input processing triggers
WidgetBase::ShowVirtualKeyboard()(widget_base_input_handler.cc:465) ->UpdateTextInputStateInternal(true, false):GetTextInputType()at line 1265 forcesUpdateStyleAndLayoutTree(), detaching the plugin’s layout object and queuing it for deferred disposal.- At scope exit,
WebPluginContainerImpl::Dispose()callsDestroy(). - 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().
- When
GetTextInputType()returns, line 1269 readsthis->client_and invokes virtual methodFrameWidget()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.