Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Digital Credentials
DescriptionUse after free in Digital Credentials
ComponentDigital Credentials
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.
Tracker519728275
Fix commit4059bc49eb12 (chromium/src) +13/-3
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-06-03 (per vendor advisory)
Fix landed2026-06-10 (public git)
Disclosed2026-06-16
Patch gap6 days — fix public in source before the advisory

Background

`DigitalIdentityMultiStepDialog`
the Views-based controller object for the multi-step Digital Credentials UI that owns a dialog_ handle to the widget it creates.
`constrained_window::ShowWebModalDialogViews`
a helper that creates and shows a web-modal dialog widget and returns the resulting views::Widget*.
Nested message loop
a re-entrant event loop that runs synchronously inside a call (here, spun while exiting HTML fullscreen), allowing other tasks to run and objects to be destroyed before the outer call returns.
`base::WeakPtr` / `base::WeakPtrFactory`
a Chromium smart-pointer pair where the factory hands out weak references that become null once the owning object is destroyed, so callers can test liveness before dereferencing.

Root Cause Analysis

In the dialog-creation path (the commit subject names DigitalIdentityMultiStepDialog::TryShow; the @@ -346 hunk header does not confirm the enclosing function name, so it is (inferred)), the pre-patch code wrote dialog_ = constrained_window::ShowWebModalDialogViews(new_dialog_delegate.release(), web_contents_.get())->GetWeakPtr(); — assigning the returned widget’s weak pointer directly into the this->dialog_ member. As the removed comment and commit message state, ShowWebModalDialogViews can spin a nested message loop (by exiting HTML fullscreen mode) that synchronously destroys this, so control returns to a statement that writes to dialog_ on a freed object, and the following SecurityDialogTracker::GetInstance()->AddSecurityDialog(dialog_.get()) also reads the freed member. The invariant violated is that this must remain live across a call that can re-enter the message loop before its members are touched again.

The fix captures weak_ptr = weak_ptr_factory_.GetWeakPtr() before the call, stores the widget in a local views::Widget* widget, and adds if (!weak_ptr) { return; } so member access resumes only when this is still alive; the new weak_ptr_factory_ member in the header backs the check. This works because the liveness test runs after the re-entrant call but strictly before any use of dialog_, converting the use-after-free into an early return.

Key insight
The single mistake is dereferencing and writing to this’s members (dialog_) immediately after a call that can synchronously destroy this via a nested message loop, with no liveness check in between. The fix interposes a base::WeakPtr liveness guard that early-returns when the object was freed during the call.

Attack Path

  1. Reach the Digital Credentials dialog path A page invokes the Digital Credentials flow, causing the browser to construct a DigitalIdentityMultiStepDialog and enter the code that calls constrained_window::ShowWebModalDialogViews to show the modal widget.
  2. Trigger a nested message loop that frees the dialog The dialog show path exits HTML fullscreen mode, which spins a nested message loop; during that loop the DigitalIdentityMultiStepDialog is synchronously destroyed (e.g. the flow is torn down), freeing this. (inferred) — the precise event sequence that destroys the object during fullscreen exit is described by the commit message but not shown in the diff.
  3. Use-after-free on return When ShowWebModalDialogViews returns, the pre-patch code executes dialog_ = ...->GetWeakPtr(), a write into the freed object, followed by AddSecurityDialog(dialog_.get()), a read of the freed member.
  4. Downstream corruption / escalation Controlling the timing and heap reuse of the freed DigitalIdentityMultiStepDialog could turn the write-after-free into a corruption primitive. (inferred) — the diff proves only the invalid access; it demonstrates no specific read/write primitive, memory-disclosure, or code-execution outcome.

Impact Assessment

An attacker who can drive the Digital Credentials dialog into destruction during the fullscreen-exit nested message loop causes a use-after-free (write-after-free and a subsequent read) on the freed DigitalIdentityMultiStepDialog, in the browser process where the Views UI runs. Preconditions are triggering the Digital Credentials flow and inducing the synchronous teardown while ShowWebModalDialogViews re-enters the loop. Any escalation to exploitable memory corruption or code execution is (inferred) and not demonstrated by the patch; the advisory classifies the issue only as a use-after-free in Digital Credentials and reports no in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
modified

Files Changed

  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h

Audit Directions

  • Member access after re-entrant UI calls
    Audit any code that assigns to or reads this members immediately after ShowWebModalDialogViews, fullscreen-mode transitions, or other calls that can spin a nested message loop, and require a WeakPtr liveness check in between.
  • Chained calls on values from blocking calls
    Flag patterns like member_ = SomeShowCall(...)->Method() where the show call may synchronously destroy the object, since the trailing assignment and dereference run on a possibly-freed this.
  • Dialog controllers lacking a WeakPtrFactory
    Review other Views dialog/controller classes in chrome/browser/ui/views/digital_credentials/ and sibling directories that create modal dialogs but keep no base::WeakPtrFactory, as they cannot self-check liveness across re-entrant show paths.
From 4059bc49eb1209be38510ad6fe4dba00fd8c1771 Mon Sep 17 00:00:00 2001
From: Mohamed Amir Yosef <mamir@chromium.org>
Date: Wed, 10 Jun 2026 04:52:09 -0700
Subject: [PATCH] [DC] Fix UAF in DigitalIdentityMultiStepDialog::TryShow

The dialog object could be synchronously destroyed during a nested
message loop spun by exiting HTML fullscreen mode in
`ShowWebModalDialogViews`. This causes a write-after-free when
`TryShow` returns and attempts to assign the returned widget to
`this->dialog_`.

This CL introduces a `base::WeakPtrFactory` to
`DigitalIdentityMultiStepDialog` and checks the liveness of `this`
using a `WeakPtr` before accessing member variables after the dialog
creation call returns.

Fixed: 519728275
Change-Id: Ifc3bf9eaa5bfa501d3e8d967df5c8252a6fdcad9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7914992
Commit-Queue: Rafał Godlewski <rgod@google.com>
Commit-Queue: Mohamed Amir Yosef <mamir@chromium.org>
Reviewed-by: Rafał Godlewski <rgod@google.com>
Cr-Commit-Position: refs/heads/main@{#1644566}
---

diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
index 97b80bb..52418e4 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
@@ -346,10 +346,18 @@
                    std::move(custom_body_field));
 
   if (new_dialog_delegate) {
+    base::WeakPtr<DigitalIdentityMultiStepDialog> weak_ptr =
+        weak_ptr_factory_.GetWeakPtr();
     // views::Widget takes ownership of `new_dialog_delegate`.
-    dialog_ = constrained_window::ShowWebModalDialogViews(
-                  new_dialog_delegate.release(), web_contents_.get())
-                  ->GetWeakPtr();
+    views::Widget* widget = constrained_window::ShowWebModalDialogViews(
+        new_dialog_delegate.release(), web_contents_.get());
+    // `ShowWebModalDialogViews` can spin a nested message loop and
+    // synchronously destroy `this`. Check `weak_ptr` before accessing member
+    // variables.
+    if (!weak_ptr) {
+      return;
+    }
+    dialog_ = widget->GetWeakPtr();
     extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog(
         dialog_.get());
   }
diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
index 2b355e6..d94980b 100644
--- a/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
+++ b/chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
@@ -85,6 +85,8 @@
   base::WeakPtr<content::WebContents> web_contents_;
 
   base::WeakPtr<views::Widget> dialog_;
+
+  base::WeakPtrFactory<DigitalIdentityMultiStepDialog> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_DIGITAL_CREDENTIALS_DIGITAL_IDENTITY_MULTI_STEP_DIALOG_H_
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Browser-Process Use-After-Free in DigitalIdentityMultiStepDialog::TryShow

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 browser-process use-after-free vulnerability exists in DigitalIdentityMultiStepDialog::TryShow. When displaying a web-modal dialog, the hosting WebContents can be synchronously destroyed during a nested message loop spun by exiting fullscreen mode. This synchronously destroys the dialog object, leaving the method to perform unsafe write and read operations on the freed instance upon return.

Affected files:

  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc
  • chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h

Estimated timestamp from git blame: 2024-07-23

Root Cause Analysis

A potential heap use-after-free (UAF) vulnerability exists in DigitalIdentityMultiStepDialog::TryShow due to a re-entrant destruction path during dialog creation. When displaying a web modal, the hosting WebContents can be synchronously destroyed, leading to the deletion of the dialog object. Upon return from ShowWebModalDialogViews, the method performs write and read operations on the freed this instance.

The vulnerable code is located in chrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.cc:

if (new_dialog_delegate) {
  // views::Widget takes ownership of `new_dialog_delegate`.
  dialog_ = constrained_window::ShowWebModalDialogViews(
                new_dialog_delegate.release(), web_contents_.get())
                ->GetWeakPtr();
  extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog(
      dialog_.get());
}

There is no self-liveness guard after ShowWebModalDialogViews returns. The class has no base::WeakPtrFactory<DigitalIdentityMultiStepDialog>, so a self-liveness check is currently impossible without a code change. The written member dialog_ is a base::WeakPtr<views::Widget>, which is not protected by MiraclePtr.

Synchronous Destruction Path

  1. ShowWebModalDialogViews calls ShowModalDialog, which delegates to WebContentsModalDialogManager::ShowDialogWithManager.
  2. This invokes BlockWebContentsInteraction(true), which calls BrowserWindowModalDialogDelegate::SetWebContentsBlocked(web_contents, true).
  3. If the tab is currently in HTML fullscreen, SetWebContentsBlocked calls web_contents->ExitFullscreen(true) to drop fullscreen.
  4. WebContentsImpl::ExitFullscreenMode invokes delegate_->ExitFullscreenModeForTab(this).
  5. As documented in the WebContentsImpl source code, this delegate call may spin a nested message loop (or wait for window server events) while adjusting the platform window state.
  6. While this nested loop is spinning, queued tasks (such as standard tab closing or navigation commits) can run, which synchronously destroys the hosting WebContents and the underlying RenderFrameHost.
  7. The destruction of the RenderFrameHost synchronously deletes the associated DocumentService (DigitalIdentityRequestImpl), which deletes the DigitalIdentityProviderDesktop and consequently the DigitalIdentityMultiStepDialog (this).
  8. Control then returns to TryShow, which attempts to write to this->dialog_ and read from it via dialog_.get(), resulting in a Use-After-Free.

Suggested / Potential Trigger Steps

Note: These are potential steps based on static code analysis; our tooling agent does not currently have the ability to run code or verify a live proof of concept.

  1. A web page in HTML fullscreen initiates a low-risk Digital Credentials API request via navigator.credentials.get using a gesture.
  2. The request bypasses the security interstitial and invokes DigitalIdentityMultiStepDialog::TryShow to display the QR code modal.
  3. TryShow calls ShowWebModalDialogViews, forcing the browser to exit fullscreen.
  4. Exiting fullscreen spins a nested loop during which the tab is closed or navigated, synchronously deleting the WebContents and the DigitalIdentityMultiStepDialog object.
  5. When ShowWebModalDialogViews returns, the method writes the return value to this->dialog_ on the freed this allocation.

Suggested Fix

Add a base::WeakPtrFactory to the DigitalIdentityMultiStepDialog class. Capture a weak reference to this before calling ShowWebModalDialogViews, and use a self-liveness guard to return early if this has been deleted:

if (new_dialog_delegate) {
  base::WeakPtr<DigitalIdentityMultiStepDialog> weak_this = weak_ptr_factory_.GetWeakPtr();
  views::Widget* widget = constrained_window::ShowWebModalDialogViews(
      new_dialog_delegate.release(), web_contents_.get());
  if (!weak_this) {
    return;
  }
  dialog_ = widget->GetWeakPtr();
  extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog(
      dialog_.get());
}

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