CVE-2026-13026
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc |
modified | |
switchchrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc |
modified |
Files Changed
chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.ccchrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h
Patch
From 8692e3c30184e81f5dd3cb9973aeef0464c101d5 Mon Sep 17 00:00:00 2001 From: Mohamed Amir Yosef <[email protected]> Date: Fri, 12 Jun 2026 10:52:31 -0700 Subject: [PATCH] Fix Potential Use-After-Free in DigitalIdentitySafetyInterstitialControllerDesktop This CL fixes a potential Use-After-Free (UAF) vulnerability in the desktop implementation of the digital credentials safety interstitial. The issue occurs because ShowInterstitialImpl takes a content::WebContents by reference and dereferences it after ShowWebModal() returns without verifying its liveness. On macOS, this can trigger a UAF if the WebContents is destroyed synchronously. The fix introduces a liveness check using the existing weak pointer before accessing the WebContents after ShowWebModal(). Fixed: 519728279 Change-Id: I198d30fde3d8cf41d70586fde5d856061b080591 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7933168 Reviewed-by: Christian Biesinger <[email protected]> Commit-Queue: Mohamed Amir Yosef <[email protected]> Cr-Commit-Position: refs/heads/main@{#1646087} --- diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc b/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc index 462aebd..7c3a4be 100644 --- a/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc +++ b/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc @@ -75,7 +75,7 @@ interstitial_type_ = interstitial_type; callback_ = std::move(callback); - ShowInterstitialImpl(web_contents, /*was_request_aborted=*/false); + ShowInterstitialImpl(/*was_request_aborted=*/false); return base::BindOnce( &DigitalIdentitySafetyInterstitialControllerDesktop::Abort, weak_ptr_factory_.GetWeakPtr()); @@ -87,12 +87,14 @@ } dialog_widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified); - ShowInterstitialImpl(*web_contents_, /*was_request_aborted*/ true); + ShowInterstitialImpl(/*was_request_aborted=*/true); } void DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitialImpl( - content::WebContents& web_contents, bool was_request_aborted) { + if (!web_contents_) { + return; + } int body_resource_id = 0; int negative_button_label_resource_id = 0; switch (interstitial_type_) { @@ -170,12 +172,18 @@ formatted_origin))); } dialog_widget_ = constrained_window::ShowWebModal( - dialog_model_builder.Build(), &web_contents); + dialog_model_builder.Build(), web_contents_.get()); extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog( dialog_widget_); + // ShowWebModal() can synchronously drop fullscreen mode. On macOS, this + // can spin a nested run loop that processes a tab close, potentially + // destroying the WebContents. Check liveness before observing it. + if (!web_contents_) { + return; + } close_on_navigation_observer_ = std::make_unique<CloseOnNavigationObserver>(); - close_on_navigation_observer_->Observe(web_contents); + close_on_navigation_observer_->Observe(*web_contents_); } void DigitalIdentitySafetyInterstitialControllerDesktop::OnDialogClosed( diff --git a/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h b/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h index 3338afc..e45f4851 100644 --- a/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h +++ b/chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h @@ -57,8 +57,7 @@ void Abort(); - void ShowInterstitialImpl(content::WebContents& web_contents, - bool was_request_aborted); + void ShowInterstitialImpl(bool was_request_aborted); void OnDialogClosed(DigitalIdentityInterstitialClosedReason reason);
Original Bug Report
Potential Use-After-Free in DigitalIdentitySafetyInterstitialControllerDesktop
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: DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitialImpl takes a content::WebContents by reference and uses it again after calling ShowWebModal(). On macOS, exiting fullscreen synchronously during modal display can trigger a nested run loop that processes a tab close, leading to immediate destruction of the WebContents. Consequently, the subsequent call to Observe() dereferences a dangling WebContents& reference, triggering a Use-After-Free (UAF) in the browser process.
Affected files:
chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.ccchrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h
Estimated timestamp from git blame: 2024-08-20
Detailed Writeup
Root Cause Analysis
A potential Use-After-Free (UAF) vulnerability exists in the desktop implementation of the digital credentials safety interstitial.
In DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitialImpl (defined in chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc), the content::WebContents object is taken by reference (content::WebContents& web_contents) and dereferenced again after ShowWebModal() returns, without any liveness verification:
void DigitalIdentitySafetyInterstitialControllerDesktop::ShowInterstitialImpl(
content::WebContents& web_contents,
bool was_request_aborted) {
...
dialog_widget_ = constrained_window::ShowWebModal(
dialog_model_builder.Build(), &web_contents); // <--- Can synchronously destroy web_contents
extensions::SecurityDialogTracker::GetInstance()->AddSecurityDialog(
dialog_widget_);
close_on_navigation_observer_ = std::make_unique<CloseOnNavigationObserver>();
close_on_navigation_observer_->Observe(web_contents); // <--- UAF: web_contents may be dangling
}
Inside CloseOnNavigationObserver::Observe(), it immediately calls web_contents.GetWeakPtr() which performs a virtual call on the deleted WebContents instance:
void DigitalIdentitySafetyInterstitialControllerDesktop::CloseOnNavigationObserver::Observe(
content::WebContents& web_contents) {
web_contents_ = web_contents.GetWeakPtr(); // <--- Virtual call via freed vtable
...
}
Synchronous Destruction Path
When ShowWebModal is called, it blocks the web contents interaction. If the tab is currently in HTML fullscreen mode (content::FullscreenMode::kContent), the browser forces it to exit fullscreen via web_contents->ExitFullscreen(true) to ensure that the interstitial dialog is shown with proper window decorations.
On macOS, exiting native fullscreen spins a synchronous AppKit nested message/event loop inside -[NSWindow toggleFullScreen:]. If a tab close or detach request is processed during this synchronous event spin, the WebContentsImpl is destroyed immediately. When control returns up the stack, constrained_window::ShowWebModalDialogViews passes its CHECK(weak_widget) because the views widget close is posted asynchronously, and ShowInterstitialImpl continues execution with a dangling web_contents reference.
Since this reference is held on the stack, it is not protected by MiraclePtr/BackupRefPtr.
Suggested/Potential Replay Steps
Note: These are potential steps. Our security review tools do not currently have the capability to execute proof-of-concept exploit code locally.
- On macOS, a script-opened tab (closable via script) is navigated to an attacker-controlled origin.
- The user initiates HTML fullscreen via a gesture (
document.documentElement.requestFullscreen()). - The user initiates a high-risk digital credentials request via a gesture, triggering the safety interstitial dialog path.
- While the macOS native fullscreen exit animation runs (which spins the nested run loop), a script-initiated close or detach event (
window.close()) is processed. - The
WebContentsis synchronously destroyed, leaving theweb_contentsreference inShowInterstitialImpldangling. - The browser crashes or encounters a memory safety violation (UAF) upon executing
Observe(web_contents).
Suggested Fix
To resolve this issue, verify the liveness of the WebContents before accessing it after the ShowWebModal call. The controller class already maintains a weak pointer to the WebContents (web_contents_), which is set inside ShowInterstitial:
if (!web_contents_) {
return;
}
close_on_navigation_observer_ = std::make_unique<CloseOnNavigationObserver>();
close_on_navigation_observer_->Observe(*web_contents_);
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.