CVE-2026-12439
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/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.ccchrome/browser/ui/views/digital_credentials/digital_identity_multi_step_dialog.h
Patch
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_
Original Bug Report
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.ccchrome/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
ShowWebModalDialogViewscallsShowModalDialog, which delegates toWebContentsModalDialogManager::ShowDialogWithManager.- This invokes
BlockWebContentsInteraction(true), which callsBrowserWindowModalDialogDelegate::SetWebContentsBlocked(web_contents, true). - If the tab is currently in HTML fullscreen,
SetWebContentsBlockedcallsweb_contents->ExitFullscreen(true)to drop fullscreen. WebContentsImpl::ExitFullscreenModeinvokesdelegate_->ExitFullscreenModeForTab(this).- As documented in the
WebContentsImplsource code, this delegate call may spin a nested message loop (or wait for window server events) while adjusting the platform window state. - While this nested loop is spinning, queued tasks (such as standard tab closing or navigation commits) can run, which synchronously destroys the hosting
WebContentsand the underlyingRenderFrameHost. - The destruction of the
RenderFrameHostsynchronously deletes the associatedDocumentService(DigitalIdentityRequestImpl), which deletes theDigitalIdentityProviderDesktopand consequently theDigitalIdentityMultiStepDialog(this). - Control then returns to
TryShow, which attempts to write tothis->dialog_and read from it viadialog_.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.
- A web page in HTML fullscreen initiates a low-risk Digital Credentials API request via
navigator.credentials.getusing a gesture. - The request bypasses the security interstitial and invokes
DigitalIdentityMultiStepDialog::TryShowto display the QR code modal. TryShowcallsShowWebModalDialogViews, forcing the browser to exit fullscreen.- Exiting fullscreen spins a nested loop during which the tab is closed or navigated, synchronously deleting the
WebContentsand theDigitalIdentityMultiStepDialogobject. - When
ShowWebModalDialogViewsreturns, the method writes the return value tothis->dialog_on the freedthisallocation.
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.