CVE-2026-13036
Overview
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
Patch
From 6b6931e5c44fc5fe912a04d4c67503a770b07e3d Mon Sep 17 00:00:00 2001 From: Dave Tapuska <[email protected]> Date: Mon, 15 Jun 2026 08:14:28 -0700 Subject: [PATCH] Prevent use-after-free in WidgetBase::UpdateSurfaceAndScreen. Add a weak pointer check after calling client_->OrientationChanged() because this call can cause the WidgetBase object to be destroyed. Bug: 523308824, 523711130 Change-Id: If70c28a4a616b4909dade238d8c39b001956fd05 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7930273 Reviewed-by: Vladimir Levin <[email protected]> Commit-Queue: Vladimir Levin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1646821} --- diff --git a/third_party/blink/renderer/platform/widget/widget_base.cc b/third_party/blink/renderer/platform/widget/widget_base.cc index 70a4733..ce8009c 100644 --- a/third_party/blink/renderer/platform/widget/widget_base.cc +++ b/third_party/blink/renderer/platform/widget/widget_base.cc @@ -1145,7 +1145,11 @@ ShouldRecordBeginMainFrameMetrics() ? DocumentUpdateReason::kBeginMainFrame : DocumentUpdateReason::kTest; + auto weak_this = weak_ptr_factory_.GetWeakPtr(); client_->UpdateLifecycle(WebLifecycleUpdate::kAll, lifecycle_reason); + if (!weak_this) { + return; + } client_->SetSuppressFrameRequestsWorkaroundFor704763Only(false); } @@ -1844,8 +1848,13 @@ screen_infos_.current().display_color_spaces); } - if (orientation_changed) + if (orientation_changed) { + auto weak_this = weak_ptr_factory_.GetWeakPtr(); client_->OrientationChanged(); + if (!weak_this) { + return; + } + } client_->DidUpdateSurfaceAndScreen(previous_original_screen_infos); }
Original Bug Report
Potential Use-After-Free in WidgetBase::UpdateVisualState via ResizeObserver
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 exists in WidgetBase::UpdateVisualState. If a ResizeObserver script executes synchronously during a lifecycle update and triggers the destruction of the widget via a nested message loop, subsequent accesses to WidgetBase members result in a UAF.
Affected files:
third_party/blink/renderer/platform/widget/widget_base.ccthird_party/blink/renderer/core/frame/web_frame_widget_impl.ccthird_party/blink/renderer/core/frame/local_frame_view.cc
Estimated timestamp from git blame: Unknown (Google3 checkout)
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in WidgetBase::UpdateVisualState within the Blink renderer. The issue arises because WidgetBase::UpdateVisualState calls client_->UpdateLifecycle(), which can trigger the synchronous execution of ResizeObserver callbacks. An attacker can use these callbacks to force the synchronous destruction of the WidgetBase object. When control returns to UpdateVisualState, the code attempts a virtual method call on a member of the now-freed this pointer, leading to a UAF.
Vulnerability Details
The vulnerable code is in third_party/blink/renderer/platform/widget/widget_base.cc:
void WidgetBase::UpdateVisualState() {
// ...
DocumentUpdateReason lifecycle_reason = ...;
client_->UpdateLifecycle(WebLifecycleUpdate::kAll, lifecycle_reason);
client_->SetSuppressFrameRequestsWorkaroundFor704763Only(false);
}
The call to UpdateLifecycle eventually reaches LocalFrameView::RunResizeObserverSteps(). This function executes within a ScriptForbiddenScope::AllowUserAgentScript block, which temporarily allows arbitrary JavaScript to run when it invokes ResizeObserver callbacks.
While window.close() defers frame detachment to an asynchronous task, an attacker can spin up a nested message loop by calling window.print() immediately after window.close(). The nested message loop processes pending IPC messages, including the Close request.
This invokes WebFrameWidgetImpl::Close(), which frees the WidgetBase:
// third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
void WebFrameWidgetImpl::Close(DetachReason detach_reason) {
// ...
widget_base_->Shutdown(delay_release);
widget_base_.reset(); // WidgetBase is freed here
// ...
}
Notably, WidgetBase::Shutdown() calls DisconnectLayerTreeView(), which nulls out the LayerTreeView’s delegate (delegate_ = nullptr). Because delegate_ is the raw_ptr pointing to the WidgetBase, clearing it drops the MiraclePtr/BackupRefPtr reference count. This causes the WidgetBase memory to be genuinely freed back to the allocator, rather than being quarantined.
When the nested loop exits and the JS callback completes, control returns to WidgetBase::UpdateVisualState(). The code then attempts to call client_->SetSuppressFrameRequestsWorkaroundFor704763Only(false). Since this is dangling, it reads an attacker-controlled client_ pointer and performs a virtual method call on a freed object.
Potential Attacker Steps
Note: These are suggested steps; our tooling does not yet execute code to provide a working PoC.
- A malicious webpage registers a
ResizeObserverwith a JavaScript callback and triggers a layout change to schedule a main frame update. - The rendering pipeline calls
WidgetBase::UpdateVisualState(), which eventually triggers the JS callback. - Inside the callback, the script calls
window.close()followed immediately bywindow.print(). - The
window.print()dialog spins a nested message loop, which processes theCloseIPC task and synchronously destroys theWidgetBaseobject. - During the nested loop, the attacker sprays the heap (e.g., using Web Workers or timed tasks) to overwrite the freed
WidgetBasememory with a forged object. - The forged object contains a fake
client_pointer that points to a fake vtable. - When the
window.print()dialog is dismissed, C++ execution resumes inWidgetBase::UpdateVisualState(). The virtual method call onthis->client_jumps to the attacker’s shellcode.
Suggested Fix
Similar to how it is handled in WidgetBase::BeginMainFrame(), the code should use a WeakPtr to verify that WidgetBase is still alive after the call to UpdateLifecycle():
auto weak_this = weak_ptr_factory_.GetWeakPtr();
client_->UpdateLifecycle(WebLifecycleUpdate::kAll, lifecycle_reason);
if (!weak_this) {
return;
}
client_->SetSuppressFrameRequestsWorkaroundFor704763Only(false);
Evaluated with Chrome root at commit: 65b3256311f3ab6fb9870eaa522de7e6dd2663bb
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.