CVE-2026-12440
Overview
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.
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
- 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 theShowDigitalIdentityInterstitialcall. - Induce the interstitial and fullscreen state
The request reaches the branch that shows the interstitial while the
WebContentsis in a state (fullscreen on Windows) thatShowDigitalIdentityInterstitialwill synchronously exit. - Re-enter and destroy the object
Exiting fullscreen spins the message loop, allowing a task that tears down the
WebContentsand theDigitalIdentityRequestImpl(this) to run synchronously inside the call, as described by the patch comment. - Use the freed object
Control returns from
ShowDigitalIdentityInterstitialand the pre-patch code assigns the returned callback toupdate_interstitial_on_abort_callback_, a member of the now-freedthis, producing a use-after-free write. - Escalation
Turning the freed-memory write into a controlled read/write primitive or code execution is not demonstrated by the diff
(inferred).
Impact Assessment
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
| Function | Change | Notes |
|---|---|---|
ifcontent/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-callSearch 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
thismay be freed before the assignment lands. - Synchronous re-entrancy via UI/fullscreenAudit browser-process paths that trigger fullscreen exit, tab/
WebContentsteardown, or modal UI mid-method, and verify each protectsthiswith abase::WeakPtrself-guard or defers the continuation. - `WebContents`-lifetime assumptions in DigitalCredentialsReview other
DigitalIdentityRequestImplandprovider_calls that dereferencerender_frame_host(),WebContents, orthisafter callbacks/nested calls, since the same lifetime invariant may be assumed elsewhere.
Patch
From 3b2421d4fccc330800af2443821d11fbb0c8e32e Mon Sep 17 00:00:00 2001 From: Mohamed Amir Yosef <[email protected]> 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 <[email protected]> Reviewed-by: Christian Biesinger <[email protected]> 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.