Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Digital Credentials
DescriptionUse after free in Digital Credentials
ComponentDigital Credentials
Bug ClassUAF
Tracker519728279
Fix commit8692e3c30184 (chromium/src) +14/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-23

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.cc
modified
switch
chrome/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.cc
  • chrome/browser/ui/views/digital_credentials/digital_identity_safety_interstitial_controller_desktop.h
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);
 
Loading diff…

Original Bug Report

reported by [email protected]

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.cc
  • chrome/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.

  1. On macOS, a script-opened tab (closable via script) is navigated to an attacker-controlled origin.
  2. The user initiates HTML fullscreen via a gesture (document.documentElement.requestFullscreen()).
  3. The user initiates a high-risk digital credentials request via a gesture, triggering the safety interstitial dialog path.
  4. 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.
  5. The WebContents is synchronously destroyed, leaving the web_contents reference in ShowInterstitialImpl dangling.
  6. 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.

View on issue tracker