CVE-2026-15117
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/payments/content/secure_payment_confirmation_controller.cc |
modified |
Files Changed
components/payments/content/secure_payment_confirmation_controller.cc
Patch
From b87b4b251a81a1e17b4388046d56f93765b646b3 Mon Sep 17 00:00:00 2001 From: Xuehui Chen <[email protected]> Date: Fri, 19 Jun 2026 15:24:44 -0700 Subject: [PATCH] [SPC] Guard SPC Controller destruction during dialog close. A `potential` UAF vulnerability exists in the browser process due to synchronous reentrancy inside SecurePaymentConfirmationController. This can happen if platform-specific synchronous activation dispatch from native_widget_->Close() that reaches ExtensionPopup::OnWidgetDestroying() inside the same call stack. Or CloseDialog() itself trigger a controller destruction. This change adds a guard to the `CloseDialog()` method that ensures the call inside SecurePaymentConfirmationController will not continue to prevent a memory crash. Fixed: 522568496 Change-Id: Ifc0eaaebc42db33bb309e850881722df13cad35d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7959279 Reviewed-by: Slobodan Pejic <[email protected]> Reviewed-by: Darwin Yang <[email protected]> Commit-Queue: Xuehui Chen <[email protected]> Cr-Commit-Position: refs/heads/main@{#1649831} --- diff --git a/components/payments/content/secure_payment_confirmation_controller.cc b/components/payments/content/secure_payment_confirmation_controller.cc index d0e4f98b..a232b58c 100644 --- a/components/payments/content/secure_payment_confirmation_controller.cc +++ b/components/payments/content/secure_payment_confirmation_controller.cc @@ -130,7 +130,15 @@ SecurePaymentRequestOutcome::kAccept); is_dialog_showing_ = false; + // CloseDialog() -> Widget::Close() can potentially synchronously trigger + // activation observers that destroy the payment window's WebContents. + // This self weak pointer guard can prevent potential UAF if controller is + // synchronously deleted inside CloseDialog(). + auto weak_this = weak_ptr_factory_.GetWeakPtr(); CloseDialog(); + if (!weak_this) { + return; + } if (!request_) { return; @@ -163,7 +171,13 @@ SecurePaymentRequestOutcome::kAnotherWay); is_dialog_showing_ = false; + + // CloseDialog() can potentially delete `this`. See OnConfirm() above. + auto weak_this = weak_ptr_factory_.GetWeakPtr(); CloseDialog(); + if (!weak_this) { + return; + } if (!request_) { return; @@ -189,7 +203,12 @@ } is_dialog_showing_ = false; + // CloseDialog() can potentially delete `this`. See OnConfirm() above. + auto weak_this = weak_ptr_factory_.GetWeakPtr(); CloseDialog(); + if (!weak_this) { + return; + } if (!request_) { return; @@ -209,7 +228,12 @@ } is_dialog_showing_ = false; + // CloseDialog() can potentially delete `this`. See OnConfirm() above. + auto weak_this = weak_ptr_factory_.GetWeakPtr(); CloseDialog(); + if (!weak_this) { + return; + } if (!request_) { return;
Original Bug Report
Potential Browser-Process Use-After-Free in SecurePaymentConfirmationController
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 use-after-free (UAF) vulnerability exists in the browser process due to synchronous reentrancy inside SecurePaymentConfirmationController. Calling CloseDialog() on the controller can synchronously trigger window activation events that destroy the hosting WebContents, the DocumentService PaymentRequest, and the controller itself. Unwinding back to the controller’s handlers leads to a UAF read and copy of the freed controller’s request_ pointer.
Affected files:
components/payments/content/secure_payment_confirmation_controller.ccchrome/browser/ui/views/payments/secure_payment_confirmation_dialog_view.ccchrome/browser/payments/chrome_payment_request_delegate.cccomponents/payments/content/payment_request.cc
Estimated timestamp from git blame: 2020-08-27
Root Cause Analysis
Four user-interaction handlers in SecurePaymentConfirmationController (OnCancel, OnAnotherWay, OnOptOut, and the error branch of OnConfirm) call CloseDialog() and then dereference/copy request_ (which is this->request_) without first verifying that the controller instance (this) is still alive:
// components/payments/content/secure_payment_confirmation_controller.cc:177-200
void SecurePaymentConfirmationController::OnCancel() {
if (!is_dialog_showing_) { return; }
...
is_dialog_showing_ = false;
CloseDialog(); // [1] Can synchronously free `this`
if (!request_) { // [2] UAF read of this->request_
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PaymentRequest::OnUserCancelled,
request_)); // [3] UAF copy of this->request_
}
CloseDialog() calls view_->HideDialog(), which invokes Widget::Close(). Under certain circumstances—such as when the dialog is hosted inside an extension popup—closing the widget shifts native activation. This activation shift can synchronously trigger ExtensionPopup::OnWidgetTreeActivated, which closes the popup window and destroys its hosting WebContents because the active web modal check is bypassed once the dialog is erased from the modal manager’s list.
Because PaymentRequest is a frame-bound Mojo DocumentService, the destruction of the WebContents results in ~PaymentRequest(), which calls ChromePaymentRequestDelegate::CloseDialog() and resets the std::unique_ptr<SecurePaymentConfirmationController> (spc_dialog_). This deletes the controller, leaving a dangling this on the stack when execution unwinds back to the handlers.
Potential Trigger Steps
Note: These are potential, suggested steps to reproduce the issue. Our analysis is based on static code tracing, and we do not have a running proof of concept.
- An extension popup embeds an attacker-influenced HTTPS
<iframe>or page. - The page calls the Secure Payment Confirmation API via
new PaymentRequest(...)to display the SPC dialog on the popup’s WebContents. - The user interacts with the dialog, clicking Cancel or Opt-out.
- The controller’s callback
OnCancel()(orOnOptOut()) is triggered. - The controller calls
CloseDialog(), which synchronously initiates the popup widget destruction chain. - The popup’s
WebContentsis destroyed, which synchronously deletes thePaymentRequestDocumentService. ~PaymentRequest()triggersChromePaymentRequestDelegate::CloseDialog(), resetting the unique pointer holding theSecurePaymentConfirmationController.- Execution returns to
OnCancel(), resulting in a browser-process Use-After-Free when checkingif (!request_)and copyingrequest_.
Proposed Fix
To prevent synchronous destruction from causing a UAF, the handlers must use a self-WeakPtr guard to check if this remains alive after CloseDialog() is executed, matching in-tree precedents in sibling modules (such as AuthenticatorCommonImpl and DigitalIdentityRequestImpl).
void SecurePaymentConfirmationController::OnCancel() {
if (!is_dialog_showing_) {
return;
}
...
is_dialog_showing_ = false;
base::WeakPtr<SecurePaymentConfirmationController> weak_this = GetWeakPtr();
CloseDialog();
if (!weak_this) {
return;
}
if (!request_) {
return;
}
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&PaymentRequest::OnUserCancelled, request_));
}
Evaluated with Chrome root at commit: b2fea2e31df308d0f04e4ae47def4c4f939ee141
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.