CVE-2026-6319
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
PaymentsWindowBridgechrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java |
modified | |
ifchrome/browser/ui/android/autofill/payments/payments_window_bridge.cc |
modified |
Files Changed
chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.javachrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridgeTest.javachrome/browser/ui/android/autofill/payments/payments_window_bridge.cc
Patch
From dd2818b02583be0a2569e2a53997f08a7b48dc87 Mon Sep 17 00:00:00 2001 From: viplavkadam <[email protected]> Date: Tue, 07 Apr 2026 09:51:43 -0700 Subject: [PATCH] [BNPL][Clank] PaymentWindowBridge native pointer fix for Use-After-free Making the native pointer in Java non-final and setting it zero when the C++ object is destroyed. Guarding all JNI calls from Java to C++ with a zero-pointer check Bug: 499018889 Change-Id: I5a2956160d15236dc96598ae132339a1eeb2d3ba Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7731228 Reviewed-by: Timofey Chudakov <[email protected]> Reviewed-by: Vinny Persky <[email protected]> Commit-Queue: Viplav Kadam <[email protected]> Cr-Commit-Position: refs/heads/main@{#1610834} --- diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java index 818265c..2db1435 100644 --- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java +++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java @@ -16,7 +16,7 @@ @JNINamespace("autofill::payments") @NullMarked class PaymentsWindowBridge { - private final long mNativePaymentsWindowBridge; + private long mNativePaymentsWindowBridge; private PaymentsWindowCoordinator mPaymentsWindowCoordinator; @CalledByNative @@ -35,6 +35,11 @@ mPaymentsWindowCoordinator.closeEphemeralTab(); } + @CalledByNative + public void onNativeDestroyed() { + mNativePaymentsWindowBridge = 0; + } + PaymentsWindowCoordinator getPaymentsWindowCoordinatorForTesting() { return mPaymentsWindowCoordinator; } @@ -50,6 +55,7 @@ * @param clickedUrl The URL that the user initiated the navigation to. */ void onNavigationFinished(GURL clickedUrl) { + if (mNativePaymentsWindowBridge == 0) return; PaymentsWindowBridgeJni.get().onNavigationFinished(mNativePaymentsWindowBridge, clickedUrl); } @@ -59,6 +65,7 @@ * @param webContents The WebContents that is being observed. */ public void onWebContentsObservationStarted(WebContents webContents) { + if (mNativePaymentsWindowBridge == 0) return; PaymentsWindowBridgeJni.get() .onWebContentsObservationStarted(mNativePaymentsWindowBridge, webContents); } @@ -70,6 +77,7 @@ * imminently destroyed. */ void onWebContentsDestroyed() { + if (mNativePaymentsWindowBridge == 0) return; PaymentsWindowBridgeJni.get().onWebContentsDestroyed(mNativePaymentsWindowBridge); } diff --git a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridgeTest.java b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridgeTest.java index a58457d..2355101 100644 --- a/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridgeTest.java +++ b/chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridgeTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertNotNull; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; import org.junit.Before; import org.junit.Rule; @@ -81,4 +82,19 @@ verify(mNativeMock).onWebContentsDestroyed(AUTOFILL_PAYMENTS_WINDOW_BRIDGE_NATIVE_POINTER); } + + @Test + public void testNativeMethodsNotCalledAfterNativeDestroyed() { + PaymentsWindowBridgeJni.setInstanceForTesting(mNativeMock); + + // Simulate native object destruction. + mPaymentsWindowBridge.onNativeDestroyed(); + + // Native method calls. + mPaymentsWindowBridge.onNavigationFinished(ISSUER_URL); + mPaymentsWindowBridge.onWebContentsObservationStarted(mMerchantWebContents); + mPaymentsWindowBridge.onWebContentsDestroyed(); + + verifyNoInteractions(mNativeMock); + } } diff --git a/chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc b/chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc index f10e3000..d18a6bf 100644 --- a/chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc +++ b/chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc @@ -27,7 +27,12 @@ base::android::AttachCurrentThread(), reinterpret_cast<int64_t>(this)); } -PaymentsWindowBridge::~PaymentsWindowBridge() = default; +PaymentsWindowBridge::~PaymentsWindowBridge() { + if (java_payments_window_bridge_) { + Java_PaymentsWindowBridge_onNativeDestroyed( + base::android::AttachCurrentThread(), java_payments_window_bridge_); + } +} void PaymentsWindowBridge::OpenEphemeralTab( const GURL& url,
Original Bug Report
Use-After-Free via dangling JNI native pointer in PaymentsWindowBridge::OnWebContentsDestroyed
Steps to reproduce the problem
Continued from https://issues.chromium.org/issues/498289905.
- Checkout and apply the patch:
git checkout 14e903b7ea5de658e62e89cd56ca758a5a0c2428
git apply patch3.diff
- Build Chromium for Android:
gn gen out/Default --args='target_os="android" target_cpu="arm64" is_debug=false use_siso=false android_static_analysis="off"'
autoninja -C out/Default chrome_public_apk
- Install on a device (connect an ARM64 Android device via adb):
out/Default/bin/chrome_public_apk install
- Start the MITM proxy on the host machine:
pip install mitmproxy
mitmproxy --mode regular --listen-port 8080 -s mitm_bnpl.py
- Host the PoC files (
poc.html,poc_checkout.html,bnpl_page.html):
python3 -m http.server 8888
- Launch Chrome:
out/Default/bin/chrome_public_apk run \
--args='--enable-features=AutofillEnableBuyNowPayLater,AutofillEnableAmountExtraction,AutofillEnableAmountExtractionTesting,AutofillDisableBnplCountryCheckForTesting,AutofillEnableBuyNowPayLaterSyncing --proxy-server=http://<host>:8080 --ignore-certificate-errors'
-
Add a test credit card in Chrome Settings > Payment methods (Name: Test User, Number: 4111 1111 1111 1111, Expiry: 12/30).
-
Navigate to
http://<host>:8888/poc.htmland click “Start PoC”. -
In the new tab, tap the Card Number field, select the saved card from the autofill bar, and then select “Affirm” from the Pay Later options.
-
After the ephemeral tab (bottom sheet) appears, the parent page closes the merchant tab in 10 seconds. The browser process crashes with SIGSEGV at
0xcdcdcdcdcdcdcdcdinPaymentsWindowBridge::OnWebContentsDestroyed.
Problem Description
PaymentsWindowBridge.java stores the C++ PaymentsWindowBridge pointer as a final long field:
// PaymentsWindowBridge PaymentsWindowBridge.java:19
class PaymentsWindowBridge {
private final long mNativePaymentsWindowBridge; // [1]
...
void onWebContentsDestroyed() {
PaymentsWindowBridgeJni.get().onWebContentsDestroyed(mNativePaymentsWindowBridge); // [5]
}
}
The pointer is set once in the constructor [1] and never cleared. The final keyword makes it impossible to set it to zero.
The PaymentsWindowCoordinator registers itself as an EphemeralTabObserver on the Activity-scoped EphemeralTabCoordinator:
// PaymentsWindowCoordinator.openEphemeralTab PaymentsWindowCoordinator.java:47
void openEphemeralTab(GURL url, String title, WebContents merchantWebContents) {
...
WindowAndroid windowAndroid = merchantWebContents.getTopLevelNativeWindow();
...
mEphemeralTabCoordinator = supplier.get();
mEphemeralTabCoordinator.addObserver(this); // [2]
...
}
The EphemeralTabCoordinator is retrieved from WindowAndroid’s unowned user data [2], making it Activity-scoped – it survives individual tab closures.
The C++ ownership chain is:
WebContents (merchant tab)
-> ContentAutofillClient (WebContentsUserData)
-> ChromeAutofillClient (extends ContentAutofillClient)
-> ChromePaymentsAutofillClient (member)
-> AndroidPaymentsWindowManager (unique_ptr)
-> PaymentsWindowBridge (unique_ptr)
AndroidPaymentsWindowManager owns PaymentsWindowBridge as a unique_ptr:
// AndroidPaymentsWindowManager android_payments_window_manager.h:65
class AndroidPaymentsWindowManager : public PaymentsWindowManager,
public PaymentsWindowDelegate {
...
std::unique_ptr<PaymentsWindowBridge> payments_window_bridge_; // [3]
};
The C++ destructor is defaulted and does not notify the Java side:
// PaymentsWindowBridge::~PaymentsWindowBridge payments_window_bridge.cc:30
PaymentsWindowBridge::~PaymentsWindowBridge() = default; // [4]
When the merchant tab is closed, ~WebContentsImpl destroys WebContentsUserData entries, which chains through ~ChromeAutofillClient -> ~ChromePaymentsAutofillClient -> ~AndroidPaymentsWindowManager -> ~PaymentsWindowBridge [3][4]. The defaulted destructor [4] does not call into Java to null out mNativePaymentsWindowBridge, nor does it remove the PaymentsWindowCoordinator from the EphemeralTabCoordinator’s observer list.
Subsequently, when the ephemeral tab’s WebContents is cleaned up, PaymentsWindowCoordinator.onWebContentsDestroyed() fires through the observer registered at [2], which calls PaymentsWindowBridge.onWebContentsDestroyed() [5], passing the dangling mNativePaymentsWindowBridge to JNI. The C++ side then dereferences payments_window_delegate_ on the freed object:
// PaymentsWindowBridge::OnWebContentsDestroyed payments_window_bridge.cc:66-68
void PaymentsWindowBridge::OnWebContentsDestroyed(JNIEnv* env) {
payments_window_delegate_->WebContentsDestroyed(); // [6] UAF: `this` is freed
}
The crash occurs at [6] because this was freed at [4], and payments_window_delegate_ (a raw_ref) now contains 0xcdcdcdcdcdcdcdcd.
The same pattern was previously found and fixed in FacilitatedPaymentsPaymentMethodsControllerBridge (crbug.com/355139788). That fix made the native pointer non-final, added an onNativeDestroyed() callback to zero it out, and guarded every JNI callback with a null check. None of these protections were applied to PaymentsWindowBridge.
Additional Comments
If a signed-in Chrome instance with a US-based Google account, a linked BNPL issuer (e.g., Affirm), and a BNPL-eligible merchant page is available, this bug can be triggered from poc.html with no patches. I don’t have such account, so I used patch to reproduce. If you still think this patch inappropriate, please let me know in the comments.
Summary
Use-After-Free via dangling JNI native pointer in PaymentsWindowBridge::OnWebContentsDestroyed
Custom Questions
Type of crash:
browser
Crash state:
Please see the attached tombstone.txt
Reporter credit:
pwn2addr
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \
- https://crbug.com/355139788
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java;l=19;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowBridge.java;l=73;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/android/autofill/internal/java/src/org/chromium/chrome/browser/ui/autofill/ephemeraltab/PaymentsWindowCoordinator.java;l=56;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc;l=30;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/android/autofill/payments/payments_window_bridge.cc;l=66;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428
- https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/ui/autofill/payments/android_payments_window_manager.h;l=65;drc=14e903b7ea5de658e62e89cd56ca758a5a0c2428