← Chrome — 2026-W36

5d16106435e6be0b036514c8a5b347c4b8e78865  [FedCM] Fix UAF in content::webid::Request

severity high class UAF CVSS 8.1 signal score 16 content/browser/ patch-now AI review CONFIRMED
Structural signals: smartptr_member_added (+6.47), null_guard (+1.95), uaf_lifetime (+3.57), msg_strong (+3.0), area:content/browser/ (+1.0)

The bug at a glance

A use-after-free in the browser process (content::webid::Request). ShowModalDialog can synchronously re-enter page script (via fullscreenchange/blur events caused by the browser opening a popup/new tab), which can detach the initiating iframe and destroy the Request object; the code then continued to touch member fields (did_show_ui_, token_error_, config_url_) on the freed object. Browser-process UAFs are generally high severity. It is not critical because triggering requires a FedCM flow in a specific state (fullscreen or focusable), attacker-timed synchronous navigation/detach, and the window is narrow; it is reachable from a web page via the FedCM API.

FedCM’s Request shows a modal dialog or popup. Opening that UI can make the initiator tab exit fullscreen or lose focus, firing synchronous JS events (fullscreenchange/blur) that the page uses to detach its own iframe, destroying the Request mid-call. The post-ShowModalDialog code then operates on freed memory.

Root cause

ShowModalDialog() calls GetDialogController()->ShowModalDialog(…), which (per the added comment) opens a popup window, or a new foreground tab when the initiator is fullscreen. That browser-initiated navigation can cause the initiating tab to exit fullscreen or lose focus, synchronously dispatching page events (fullscreenchange, blur). A malicious page handling those events can detach the initiating iframe, which tears down and frees the owning Request. Control returns into ShowModalDialog, which then writes did_show_ui_ = true and reads other members on ’this’ — a freed object. The companion path ClickErrorDialogMoreDetailsForDevtools() has the same shape: after ShowModalDialog it dereferences token_error_/config_url_ via OnDismissErrorDialog. The fix captures a WeakPtr before the call and returns early if the Request was destroyed.

Key code

Guarding against synchronous self-destruction during ShowModalDialog with a WeakPtr liveness check.

+  base::WeakPtr<Request> weak_this = weak_ptr_factory_.GetWeakPtr();
   WebContents* web_contents = GetDialogController()->ShowModalDialog(...);
+  if (!weak_this) {
+    return;
+  }
   did_show_ui_ = true;

Patch walkthrough

  • content/browser/webid/request.cc — In ShowModalDialog(): before calling GetDialogController()->ShowModalDialog(…), capture weak_this = weak_ptr_factory_.GetWeakPtr(); after the call return early if !weak_this, so did_show_ui_ and later member accesses never touch a freed Request. The added comment documents the fullscreen/blur-driven synchronous detach path.
  • content/browser/webid/request.cc — In ClickErrorDialogMoreDetailsForDevtools(): same WeakPtr capture before ShowModalDialog(kErrorUrlPopup, …) and early return afterward, preventing the subsequent OnDismissErrorDialog(config_url_, token_request_status_, …) from using freed token_error_/config_url_ state.
  • content/browser/webid/request_unittest.cc — Adds RequestTest.FrameDetachDuringShowModalDialog: a TestDialogController whose ShowModalDialog synchronously NavigateAndCommit()s the WebContents to another origin to destroy the Request during the call, then asserts EXPECT_FALSE(request_) — a regression test proving the UAF path and the fix.

Background

FedCM — Federated Credential Management: a browser API letting sites request federated sign-in; content::webid::Request orchestrates the browser-side flow including showing dialogs and popups.

base::WeakPtr / WeakPtrFactory — Chromium smart pointers that invalidate when the owning object is destroyed; checking !weak_this after a potentially-reentrant call detects self-destruction.

Reentrant destruction — A synchronous callback (JS event handler) runs during a browser call and can destroy the object whose method is executing, so code after the call must re-check liveness.

Initiator fullscreen / focus — Opening FedCM UI can force the initiator tab out of fullscreen or steal focus, synchronously dispatching fullscreenchange/blur to the page.

Vulnerability window

  1. Latent bug — ShowModalDialog and ClickErrorDialogMoreDetailsForDevtools accessed member state after a call that could synchronously destroy the Request. (inferred)
  2. Discovery — Tracked as Fixed: 552751639; a frame-detach-during-dialog scenario was identified as a UAF. (inferred)
  3. Fix — 2026-09-01: WeakPtr liveness checks added around both ShowModalDialog call sites with a regression test (this commit).

Proof of concept

Conceptual only. A page would start a FedCM flow, arrange the initiator to be fullscreen (or focused) so showing the dialog fires a synchronous fullscreenchange/blur, and in that handler remove the initiating iframe to free the Request while ShowModalDialog is still on the stack. Exact triggering timing and the detach primitive are not shown in the diff; no working exploit is provided.

Exploitation

  1. Setup — Attacker page embeds a cross/same-origin iframe that initiates a FedCM request and arranges initiator fullscreen or focus so dialog display will fire a synchronous page event. (inferred)
  2. Trigger — ShowModalDialog opens the popup/new tab, synchronously dispatching fullscreenchange/blur; the handler detaches the initiating iframe, freeing the Request.
  3. Use-after-free — Control returns into ShowModalDialog, which writes did_show_ui_ and reads members on the freed Request; with heap grooming an attacker-controlled object could occupy the freed slot. (inferred)

Detection & hunting

For defenders and detection engineers:

  • ASAN/MSAN heap-use-after-free in content::webid::Request — Look for crashes with stacks through ShowModalDialog / ClickErrorDialogMoreDetailsForDevtools after a navigation/frame-detach during a FedCM dialog.
  • FedCM flow with concurrent frame detach — Audit telemetry for FedCM requests whose initiating frame is removed while a dialog is being shown; crashes clustering on did_show_ui_ writes are suspicious.

Audit directions

  • content/browser/webid — Review every call that can show UI or run the dialog controller for post-call member access without a liveness re-check; GetDialogController() calls are prime candidates.
  • Browser-initiated UI that can reenter page script — Audit other browser-process code that opens popups/tabs or exits fullscreen mid-method, since the same fullscreenchange/blur synchronous-reentry pattern can destroy ’this'.

Before / after

Loading diff…