Overview

Critical
Severity
9.6
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in DigitalCredentials
DescriptionUse after free in DigitalCredentials
ComponentDigitalCredentials
Sub-componentDC
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker519731619
Fix commit3b2421d4fccc (chromium/src) +18/-7
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-03 (per vendor advisory)
Fix landed2026-06-09 (public git)
Disclosed2026-06-16
Patch gap7 days — fix public in source before the advisory

Background

`DigitalIdentityRequestImpl`
The browser-process implementation backing the Digital Credentials API that services a renderer’s request to retrieve a digital identity credential.
`ShowDigitalIdentityInterstitial`
A provider_ method that displays a user-facing interstitial before a digital-identity request proceeds and returns an abort callback used to update or dismiss it.
`base::WeakPtr`
A smart pointer that automatically becomes null when the pointed-to object is destroyed, letting callers detect that an object was freed mid-operation.
Synchronous re-entrancy
When a call nested inside a method spins the message loop, unrelated tasks (e.g. exiting fullscreen) can run and destroy the calling object before control returns to it.

Root Cause Analysis

The vulnerable path called provider_->ShowDigitalIdentityInterstitial(...) and immediately assigned its return value to the member update_interstitial_on_abort_callback_. As the added comment states, ShowDigitalIdentityInterstitial “can synchronously exit fullscreen on Windows, which can spin the message loop and destroy the WebContents and this,” so control could return into an assignment on an object (this) that had already been freed — a write to freed memory. The invariant violated is that a member of this may only be written while this is still alive, which the code assumed held across the nested call but did not.

The fix captures base::WeakPtr<DigitalIdentityRequestImpl> weak_this before the call, stores the result in a local abort_callback, and after the call checks if (!weak_this) { return; }, only assigning update_interstitial_on_abort_callback_ = std::move(abort_callback) when the object still exists. This works because the write to the member field is now gated on the object’s survival, so the freed-object write can no longer occur.

Key insight
The single mistake was writing the interstitial-abort callback into a member of this after a synchronously re-entrant call (ShowDigitalIdentityInterstitial) that could destroy this; the fix interposes a base::WeakPtr self-guard and early-returns when the object was freed, eliminating the freed-memory write.

Attack Path

  1. Trigger a digital-identity request A page invokes the Digital Credentials API, driving the browser-process code path (inferred to be DigitalIdentityRequestImpl::Get() per the commit subject; the enclosing function is not shown in the @@ hunk header) up to the ShowDigitalIdentityInterstitial call.
  2. Induce the interstitial and fullscreen state The request reaches the branch that shows the interstitial while the WebContents is in a state (fullscreen on Windows) that ShowDigitalIdentityInterstitial will synchronously exit.
  3. Re-enter and destroy the object Exiting fullscreen spins the message loop, allowing a task that tears down the WebContents and the DigitalIdentityRequestImpl (this) to run synchronously inside the call, as described by the patch comment.
  4. Use the freed object Control returns from ShowDigitalIdentityInterstitial and the pre-patch code assigns the returned callback to update_interstitial_on_abort_callback_, a member of the now-freed this, producing a use-after-free write.
  5. Escalation Turning the freed-memory write into a controlled read/write primitive or code execution is not demonstrated by the diff (inferred).

Impact Assessment

An attacker gains a use-after-free on a DigitalIdentityRequestImpl object in the browser process, reachable from a web page that triggers a digital-identity request under conditions that cause a synchronous fullscreen exit (noted as Windows). The vendor advisory rates this critical severity and does not report in-the-wild exploitation; NVD scores it CVSS 9.6. Any progression from the freed-memory write to a read/write primitive, sandbox escape, or code execution is (inferred) rather than established by the patch.

Changed Functions

FunctionChangeNotes
if
content/browser/digital_credentials/digital_identity_request_impl.cc
modified

Files Changed

  • content/browser/digital_credentials/digital_identity_request_impl.cc

Audit Directions

  • Assign-to-member-after-nested-call
    Search for member-field assignments whose right-hand side is a call that can spin the message loop or run user-visible UI (interstitials, dialogs, fullscreen transitions), since this may be freed before the assignment lands.
  • Synchronous re-entrancy via UI/fullscreen
    Audit browser-process paths that trigger fullscreen exit, tab/WebContents teardown, or modal UI mid-method, and verify each protects this with a base::WeakPtr self-guard or defers the continuation.
  • `WebContents`-lifetime assumptions in DigitalCredentials
    Review other DigitalIdentityRequestImpl and provider_ calls that dereference render_frame_host(), WebContents, or this after callbacks/nested calls, since the same lifetime invariant may be assumed elsewhere.
From 3b2421d4fccc330800af2443821d11fbb0c8e32e Mon Sep 17 00:00:00 2001
From: Mohamed Amir Yosef <mamir@chromium.org>
Date: Tue, 09 Jun 2026 16:15:55 -0700
Subject: [PATCH] [DC] Fix UAF in DigitalIdentityRequestImpl::Get()

This CL introduces a WeakPtr self-guard around
ShowDigitalIdentityInterstitial to prevent a use-after-free if the
WebContents and DigitalIdentityRequestImpl instance are destroyed
synchronously during the call.

Fixed: 519731619
Change-Id: I027e9dd3acacc4a5c18d54468d32f1e7c3f02f50
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7914423
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: Christian Biesinger <cbiesinger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644298}
---

diff --git a/content/browser/digital_credentials/digital_identity_request_impl.cc b/content/browser/digital_credentials/digital_identity_request_impl.cc
index f5f37e3..4fc837b 100644
--- a/content/browser/digital_credentials/digital_identity_request_impl.cc
+++ b/content/browser/digital_credentials/digital_identity_request_impl.cc
@@ -654,13 +654,24 @@
     return;
   }
 
-  update_interstitial_on_abort_callback_ =
-      provider_->ShowDigitalIdentityInterstitial(
-          *WebContents::FromRenderFrameHost(&render_frame_host()), origin(),
-          *interstitial_type,
-          base::BindOnce(&DigitalIdentityRequestImpl::OnInterstitialDone,
-                         weak_ptr_factory_.GetWeakPtr(),
-                         std::move(request_to_send)));
+  // ShowDigitalIdentityInterstitial can synchronously exit fullscreen on
+  // Windows, which can spin the message loop and destroy the WebContents and
+  // `this`. We use a WeakPtr to guard against this potential UAF.
+  base::WeakPtr<DigitalIdentityRequestImpl> weak_this =
+      weak_ptr_factory_.GetWeakPtr();
+
+  auto abort_callback = provider_->ShowDigitalIdentityInterstitial(
+      *WebContents::FromRenderFrameHost(&render_frame_host()), origin(),
+      *interstitial_type,
+      base::BindOnce(&DigitalIdentityRequestImpl::OnInterstitialDone,
+                     weak_ptr_factory_.GetWeakPtr(),
+                     std::move(request_to_send)));
+
+  if (!weak_this) {
+    return;
+  }
+
+  update_interstitial_on_abort_callback_ = std::move(abort_callback);
 }
 
 void DigitalIdentityRequestImpl::Create(
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential UAF in DigitalIdentityRequestImpl::Get via Synchronous WebContents Destruction

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 write-after-free vulnerability exists in DigitalIdentityRequestImpl::Get() because it assigns an abort callback to a member variable after a synchronous UI call that can destroy ’this’. Showing the digital identity safety interstitial can trigger exiting fullscreen, which can spin the message loop and synchronously destroy the WebContents and its associated DocumentServices. The subsequent assignment to the freed object’s member variable results in browser-process memory corruption.

Affected files:

  • content/browser/digital_credentials/digital_identity_request_impl.cc

Estimated timestamp from git blame: 2024-05-24

Potential Use-After-Free in DigitalIdentityRequestImpl::Get()

A potential write-after-free (UAF) vulnerability has been identified in DigitalIdentityRequestImpl::Get(). Under certain conditions, a synchronous call to display the digital identity safety interstitial can lead to the destruction of the underlying WebContents and the DigitalIdentityRequestImpl instance. When control returns from this call, the code attempts to assign a returned abort callback to a member variable of the already-freed DigitalIdentityRequestImpl instance, resulting in a heap write-after-free in the browser process.

Note: The following steps are potential/suggested reproduction steps, as our tooling agent does not currently have the ability to run code or compile/execute proof-of-concept exploits.


Root Cause Analysis

In content/browser/digital_credentials/digital_identity_request_impl.cc, the Get method invokes the provider to show the safety interstitial:

// content/browser/digital_credentials/digital_identity_request_impl.cc:651-657
update_interstitial_on_abort_callback_ =
    provider_->ShowDigitalIdentityInterstitial(
        *WebContents::FromRenderFrameHost(&render_frame_host()), origin(),
        *interstitial_type,
        base::BindOnce(&DigitalIdentityRequestImpl::OnInterstitialDone,
                       weak_ptr_factory_.GetWeakPtr(),
                       std::move(request_to_send)));

DigitalIdentityRequestImpl is a DocumentService whose lifetime is tied to the RenderFrameHost and WebContents.

When ShowDigitalIdentityInterstitial() is called, it eventually invokes constrained_window::ShowWebModal, which triggers BlockWebContentsInteraction(true). In BrowserWindowModalDialogDelegate::SetWebContentsBlocked(), if the tab is currently in HTML fullscreen, the delegate attempts to exit fullscreen by calling web_contents->ExitFullscreen(true).

In WebContentsImpl::ExitFullscreen(), the call is forwarded to the delegate via delegate_->ExitFullscreenModeForTab(this). As noted by in-tree comments, this call can spin the message loop (particularly during native fullscreen transition updates on Windows). If a queued tab-close event or window.close() is processed during this message-loop spin, the WebContents and its associated DocumentService instances—including DigitalIdentityRequestImpl—are synchronously destroyed.

When the nested loop exits and control returns to DigitalIdentityRequestImpl::Get(), the temporary OnceClosure returned by ShowDigitalIdentityInterstitial is move-assigned into this->update_interstitial_on_abort_callback_. Since this has been deleted, this results in a write-after-free.

Since base::OnceClosure is not protected by MiraclePtr (as it holds a bare T* pointer to its internal BindStateBase), an attacker who can groom the PartitionAlloc heap during the message-loop spin could potentially hijack the virtual destructor pointer of the reallocated BindStateBase object, yielding an indirect control flow hijack inside the unsandboxed browser process.


Potential Steps to Reproduce

  1. A page enters HTML fullscreen via a user gesture.
  2. The page registers a pending task to close the tab (e.g., using a short timeout or postMessage mechanism to trigger a window-close operation).
  3. The page initiates a digital credential request via navigator.credentials.get(...) requesting a high-risk credential type (triggering the digital identity safety interstitial).
  4. The browser process handles the Mojo call and routes it to ShowDigitalIdentityInterstitial(), which blocks web contents interaction and calls ExitFullscreen().
  5. During the fullscreen exit transition, the Windows native message loop spins and executes the queued tab-close/window-close task, synchronously destroying the WebContents and freeing the DigitalIdentityRequestImpl instance.
  6. The message loop returns, control unwinds, and the return value is move-assigned into update_interstitial_on_abort_callback_ on the freed this pointer.

Suggested Fix

Mirror the WeakPtr liveness guard pattern used in other parts of DigitalIdentityRequestImpl (such as on the close-UI path in CompleteRequestWithStatus):

base::WeakPtr<DigitalIdentityRequestImpl> weak_this =
    weak_ptr_factory_.GetWeakPtr();

auto abort_callback = provider_->ShowDigitalIdentityInterstitial(
    *WebContents::FromRenderFrameHost(&render_frame_host()), origin(),
    *interstitial_type,
    base::BindOnce(&DigitalIdentityRequestImpl::OnInterstitialDone,
                   weak_ptr_factory_.GetWeakPtr(),
                   std::move(request_to_send)));

if (!weak_this) {
  return;
}

update_interstitial_on_abort_callback_ = std::move(abort_callback);

Evaluated with Chrome root at commit: 9ebf4302210513a012c901d87a2668b3aadf8cc1


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