CVE-2026-87460
Overview
Background
- `WidgetBase`
- a Blink platform object (
third_party/blink/renderer/platform/widget/widget_base.cc) that backs a renderer widget and mediates frame lifecycle and input/IME interactions with itsclient_. - `base::WeakPtr` / `weak_ptr_factory_`
- a weak-reference mechanism whose
GetWeakPtr()yields a pointer that evaluates false once the owning object is destroyed, used to detect re-entrant self-destruction. - Synchronous re-entrancy
UpdateTextInputState()andShowVirtualKeyboard()can dispatch synchronously into the browser/embedder and, as a side effect, cause theWidgetBaseitself to be torn down before control returns.
Root Cause Analysis
WidgetBase::DidBeginMainFrame called UpdateTextInputState() and then unconditionally dereferenced this via client_->DidBeginMainFrame(); likewise WidgetBase::ShowVirtualKeyboardOnElementFocus called ShowVirtualKeyboard() and continued executing on this afterward. Because those synchronous calls can trigger destruction of the WidgetBase instance, the subsequent member access ran on a freed object — a use-after-free. The invariant violated is that a method must not touch instance state after invoking a call that may synchronously free the instance.
The fix captures weak_ptr_factory_.GetWeakPtr() into weak_this before the dangerous call and returns early if !weak_this afterward, so no member access occurs once the object has been destroyed.
this after a synchronous call that could delete the WidgetBase; the fix takes a base::WeakPtr before the call and bails out early when the weak pointer has been invalidated, preventing the post-free dereference.Attack Path
- Reach the vulnerable methods
A page arranges for
WidgetBase::DidBeginMainFrameto run during frame production, or focuses a text input soWidgetBase::ShowVirtualKeyboardOnElementFocusis invoked. - Force synchronous teardown
During
UpdateTextInputState()(orShowVirtualKeyboard()), a synchronous notification to the client/embedder causes the owningWidgetBaseto be destroyed before the call returns. - Trigger the stale access
With the pre-patch code, control returns into the method and it dereferences the now-freed
this(e.g.client_->DidBeginMainFrame()), producing a use-after-free. - Exploitation
Controlling the reallocation of the freed
WidgetBasememory could yield a corrupted-object primitive leading to further compromise(inferred); the patch itself only demonstrates the freed-object access.
Impact Assessment
WidgetBase mid-call and cause the renderer to dereference freed memory, a use-after-free in the renderer process; the advisory classifies this as a use-after-free in the Platform component. Memory corruption of this kind is commonly a stepping stone to renderer code execution (inferred), but the patch does not itself demonstrate escalation beyond the freed-object access, and the advisory does not report in-the-wild exploitation.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/platform/widget/widget_base.cc |
modified |
Files Changed
third_party/blink/renderer/platform/widget/widget_base.cc
Audit Directions
- Member access after synchronous callbacksAudit methods that call into
client_/embedder or IME/keyboard code and then continue touchingthis; each such call that can re-enter and destroy the object needs aWeakPtrguard. - Lifecycle and focus/IME entry pointsReview other
WidgetBaseand widget lifecycle methods (frame begin/commit, focus, input-state updates) for the same “call then dereference” shape. - WeakPtr guard completenessWhere a
WeakPtrcheck is added, verify every later dereference (not just the immediate next one) is dominated by the check, since a single guarded call can still be followed by unguarded state access.
Patch
From 1a454d58b0eb6ab832f5db00c18bb3b7380f8df4 Mon Sep 17 00:00:00 2001 From: Dave Tapuska <[email protected]> Date: Wed, 29 Jul 2026 08:23:59 -0700 Subject: [PATCH] Prevent use-after-free in WidgetBase by adding weak pointer checks. In WidgetBase::DidBeginMainFrame and WidgetBase::ShowVirtualKeyboardOnElementFocus, synchronous calls to update text input state or show the virtual keyboard can result in the destruction of the WidgetBase instance. This CL adds weak pointer checks to safely return early if the instance is destroyed during these calls. BUG=540058837,540058837 Change-Id: I9c87677f6ec186faaefda461f2a9f0ef49e19bb3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8162668 Commit-Queue: Dave Tapuska <[email protected]> Reviewed-by: Vladimir Levin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1670274} --- diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc index 8b34b48..3880f16c 100644 --- a/third_party/blink/renderer/platform/widget/widget_base.cc +++ b/third_party/blink/renderer/platform/widget/widget_base.cc @@ -702,7 +702,11 @@ } void WidgetBase::DidBeginMainFrame() { + base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr(); UpdateTextInputState(); + if (!weak_this) { + return; + } client_->DidBeginMainFrame(); } @@ -1394,6 +1398,7 @@ } void WidgetBase::ShowVirtualKeyboardOnElementFocus() { + base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr(); #if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_IOS_TVOS) // On ChromeOS, virtual keyboard is triggered only when users leave the // mouse button or the finger and a text input element is focused at that @@ -1405,6 +1410,9 @@ #else ShowVirtualKeyboard(); #endif + if (!weak_this) { + return; + } // TODO(rouslan): Fix ChromeOS and Windows 8 behavior of autofill popup with // virtual keyboard.
Original Bug Report
Potential Use-After-Free in WidgetBase::DidBeginMainFrame
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 WidgetBase::DidBeginMainFrame because UpdateTextInputState() can synchronously trigger document layout updates and user script execution, which can destroy the WidgetBase instance. Upon return, the function performs a virtual method call on the client_ pointer from the freed WidgetBase object. This could potentially allow an attacker to execute arbitrary code in the sandboxed renderer process.
Affected files:
third_party/blink/renderer/platform/widget/widget_base.cc
Estimated timestamp from git blame: 2022-09-16
Potential Use-After-Free in WidgetBase::DidBeginMainFrame
Location
third_party/blink/renderer/platform/widget/widget_base.cc:704-707
Description
A potential Use-After-Free (UAF) vulnerability exists in WidgetBase::DidBeginMainFrame due to a missing liveness guard. Calling UpdateTextInputState() can trigger synchronous layout updates and user script execution, leading to the destruction of the WidgetBase instance. Upon return, the function performs a virtual method call on the client_ pointer of the freed WidgetBase instance, leading to a potential Remote Code Execution (RCE) primitive in the sandboxed renderer process.
Root Cause Analysis
In third_party/blink/renderer/platform/widget/widget_base.cc, WidgetBase::DidBeginMainFrame() has the following implementation:
void WidgetBase::DidBeginMainFrame() {
UpdateTextInputState();
client_->DidBeginMainFrame();
}
When UpdateTextInputState() is called, it routes to UpdateTextInputStateInternal(false, false), which eventually invokes frame_widget->TextInputInfo() to query text input metadata.
If the layout is currently dirty, InputMethodController::TextInputInfo() forces a style and layout update:
GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
During UpdateStyleAndLayout(), the document instantiates an HTMLFrameOwnerElement::PluginDisposeSuspendScope. If any <embed> or <object> elements are scheduled for detachment or disposal (due to prior DOM modifications), they are collected. When the scope exits, the deferred plugin disposals are processed via PerformDeferredPluginDispose(), which executes web_plugin_->Destroy() inside WebPluginContainerImpl::Dispose() on the main thread.
Since plugin teardown operates under ScriptForbiddenScope::AllowUserAgentScript, it can run arbitrary, synchronous user JavaScript. If this JavaScript detaches the local frame widget, it calls WebFrameWidgetImpl::Close(), which triggers widget_base_->Shutdown() and widget_base_.reset(), synchronously deleting the WidgetBase instance.
Although UpdateTextInputStateInternal contains a base::WeakPtr<WidgetBase> weak_this guard that catches this destruction and returns early, the calling function WidgetBase::DidBeginMainFrame() lacks any such guard. Once control returns to DidBeginMainFrame(), the execution attempts to access client_->DidBeginMainFrame(). Since the WidgetBase instance was freed, reading this->client_ causes a Use-After-Free. Because client_ is a virtual interface (WidgetBaseClient), this results in an exploitable virtual method dispatch.
Mitigations Checked
- MiraclePtr / BackupRefPtr (BRP): The dangling pointer is the stack-resident
thispointer (a bare pointer) and not araw_ptr<>. Additionally, all externalraw_ptrreferences toWidgetBase(e.g.,LayerTreeView::delegate_) are nullified or destroyed prior toWidgetBasedeletion, so the allocation is not quarantined. - Sibling Guards: Sibling callers of
UpdateTextInputStateInternal()(such asForceTextInputStateUpdateandOnImeEventGuardFinish) are properly guarded, butDidBeginMainFramewas missed.
Suggested Steps to Trigger (Potential)
Note: Since our security analysis tools do not have the capability to execute code, these are potential steps derived from static analysis of the codebase.
- Serve a page with a valid
HTMLInCanvasOrigin Trial token to enable theCanvasDrawElementfeature. - Embed a local subframe containing a focused
<textarea>, an<embed>or<object>plugin element, and a<canvas>registered for thepaintevent (populatingcanvas_elements_needing_onpaint_). - In the canvas synchronous
paintevent handler (which runs duringWillCommit), mutate the layout/CSS so the plugin element is scheduled to be detached (e.g.,display: none), and dirty the layout of the textarea’s document. - When
WidgetBase::DidBeginMainFrame()runs and forces a layout update insideTextInputInfo(), the plugin disposal will run synchronous attacker JavaScript. - In the plugin’s
Destroy()JS handler, detach the local frame, destroying theWidgetBaseinstance. - Reclaim/spray the heap to replace the freed
WidgetBasememory and control theclient_pointer, leading to arbitrary virtual call execution on return.
Suggested Fix
Add a base::WeakPtr check in WidgetBase::DidBeginMainFrame() to detect if the widget was destroyed during UpdateTextInputState():
void WidgetBase::DidBeginMainFrame() {
base::WeakPtr<WidgetBase> weak_this = weak_ptr_factory_.GetWeakPtr();
UpdateTextInputState();
if (!weak_this) {
return;
}
client_->DidBeginMainFrame();
}
Evaluated with Chrome root at commit: 94d9235ebe3b7276e5284f0dc5d55577ff949908
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.