Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Blink
DescriptionUse after free in Blink
ComponentBlink
Bug ClassUAF
Tracker523711130
Fix commit6b6931e5c44f (chromium/src) +10/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-23

Changed Functions

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

Files Changed

  • third_party/blink/renderer/platform/widget/widget_base.cc
From 6b6931e5c44fc5fe912a04d4c67503a770b07e3d Mon Sep 17 00:00:00 2001
From: Dave Tapuska <dtapuska@chromium.org>
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 <vmpstr@chromium.org>
Commit-Queue: Vladimir Levin <vmpstr@chromium.org>
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);
 }
Loading diff…

Original Bug Report

reported by rj...@google.com

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.cc
  • third_party/blink/renderer/core/frame/web_frame_widget_impl.cc
  • third_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.

  1. A malicious webpage registers a ResizeObserver with a JavaScript callback and triggers a layout change to schedule a main frame update.
  2. The rendering pipeline calls WidgetBase::UpdateVisualState(), which eventually triggers the JS callback.
  3. Inside the callback, the script calls window.close() followed immediately by window.print().
  4. The window.print() dialog spins a nested message loop, which processes the Close IPC task and synchronously destroys the WidgetBase object.
  5. During the nested loop, the attacker sprays the heap (e.g., using Web Workers or timed tasks) to overwrite the freed WidgetBase memory with a forged object.
  6. The forged object contains a fake client_ pointer that points to a fake vtable.
  7. When the window.print() dialog is dismissed, C++ execution resumes in WidgetBase::UpdateVisualState(). The virtual method call on this->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.

View on issue tracker