diff --git a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc index af5c7e242a309..16966ff97edc3 100644 --- a/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc +++ b/components/autofill/core/browser/payments/virtual_card_enrollment_manager_unittest.cc @@ -1068,4 +1068,93 @@ TEST_P(DownstreamEnrollmentEarlyPreflightCallCallbackParamTest, } } +// ----------------------------------------------------------------------------- +// Reproducer for a heap-use-after-free in +// VirtualCardEnrollmentManager::OnDidGetUpdateVirtualCardEnrollmentResponse(). +// +// On Android, AutofillPaymentMethodsDelegate owns this manager via unique_ptr +// and supplies a `virtual_card_enrollment_update_response_callback_` that, via +// synchronous JNI (RunBooleanCallbackAndroid -> Callback.onResult -> +// AutofillServerCardEditor lambda -> mDelegate.cleanup() -> JNI Cleanup() -> +// `delete this`), can synchronously destroy the delegate (and therefore this +// manager) while OnDidGetUpdateVirtualCardEnrollmentResponse() is still on the +// stack. The method then continues to dereference `this` (reading `state_` and +// calling `Reset()`). +// +// This test models the production ownership exactly: a unique_ptr-owned real +// VirtualCardEnrollmentManager with autofill_client == nullptr (the Android +// settings-page configuration), an Unenroll() call whose update-response +// callback synchronously destroys the manager, and the network response +// arriving via the WeakPtr-bound callback exactly as +// MultipleRequestPaymentsNetworkInterface delivers it. +// +// Under ASAN this test produces: +// ERROR: AddressSanitizer: heap-use-after-free +// in VirtualCardEnrollmentManager::OnDidGetUpdateVirtualCardEnrollmentResponse +// ----------------------------------------------------------------------------- +TEST(VirtualCardEnrollmentManagerUAFTest, + OnDidGetUpdateVirtualCardEnrollmentResponse_UseAfterFree) { + base::test::TaskEnvironment task_environment; + // Only used to provide a URLLoaderFactory + IdentityManager + PDM for the + // dependencies; *not* passed to the VirtualCardEnrollmentManager (matching + // the Android AutofillPaymentMethodsDelegate construction, which passes + // autofill_client == nullptr). + TestAutofillClient deps; + + // Mirror AutofillPaymentMethodsDelegate's ownership: it owns both the + // network interface and the VirtualCardEnrollmentManager via unique_ptr, + // and `Cleanup()` does `delete this`, destroying both. + auto network_interface = + std::make_unique( + deps.GetURLLoaderFactory(), *deps.GetIdentityManager()); + auto manager = std::make_unique( + &deps.GetPersonalDataManager().payments_data_manager(), + network_interface.get(), + /*autofill_client=*/nullptr); + + // Capture the WeakPtr-bound response callback that the manager hands to the + // network layer; in production this is invoked from + // RequestOperation::ReportOperationResult() when the Payments RPC completes. + base::OnceCallback + network_response_callback; + EXPECT_CALL(*network_interface, UpdateVirtualCardEnrollment(_, _)) + .WillOnce( + [&](const payments::UpdateVirtualCardEnrollmentRequestDetails&, + base::OnceCallback cb) { + network_response_callback = std::move(cb); + return payments::RequestId(); + }); + + // The update-response callback models RunVirtualCardEnrollmentUpdateResponse- + // Callback -> RunBooleanCallbackAndroid -> Java lambda with + // mServerCardEditorClosed == true -> mDelegate.cleanup() -> native Cleanup() + // -> `delete this` on AutofillPaymentMethodsDelegate, which destroys both + // unique_ptr members. Everything here runs synchronously on the same thread, + // exactly as the JNI path does. + manager->Unenroll( + /*instrument_id=*/112233445566, + base::BindOnce( + [](std::unique_ptr* m, + std::unique_ptr< + payments::MockMultipleRequestPaymentsNetworkInterface>* n, + payments::PaymentsAutofillClient::PaymentsRpcResult) { + // ~AutofillPaymentMethodsDelegate() destroys members in reverse + // declaration order: VCEM first, then the network interface. + m->reset(); + n->reset(); + }, + &manager, &network_interface)); + + ASSERT_TRUE(network_response_callback); + + // Simulate the Payments RPC completing on the UI thread. The WeakPtr is still + // valid (the manager has not been destroyed yet), so this enters + // OnDidGetUpdateVirtualCardEnrollmentResponse(). At line 342 the stored + // callback runs and synchronously frees `this`; lines 346-349 then read + // `state_` and call `Reset()` on the freed object. + std::move(network_response_callback) + .Run(payments::PaymentsAutofillClient::PaymentsRpcResult::kSuccess); +} + } // namespace autofill