CVE-2026-12440
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/digital_credentials/digital_identity_request_impl.cc |
modified |
Files Changed
content/browser/digital_credentials/digital_identity_request_impl.cc
Patch
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(
Original Bug Report
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
- A page enters HTML fullscreen via a user gesture.
- 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).
- The page initiates a digital credential request via
navigator.credentials.get(...)requesting a high-risk credential type (triggering the digital identity safety interstitial). - The browser process handles the Mojo call and routes it to
ShowDigitalIdentityInterstitial(), which blocks web contents interaction and callsExitFullscreen(). - During the fullscreen exit transition, the Windows native message loop spins and executes the queued tab-close/window-close task, synchronously destroying the
WebContentsand freeing theDigitalIdentityRequestImplinstance. - The message loop returns, control unwinds, and the return value is move-assigned into
update_interstitial_on_abort_callback_on the freedthispointer.
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.