diff --git a/components/autofill/content/renderer/password_generation_agent.cc b/components/autofill/content/renderer/password_generation_agent.cc index 6a7fa075b008c..8d9aa34edd7cd 100644 --- a/components/autofill/content/renderer/password_generation_agent.cc +++ b/components/autofill/content/renderer/password_generation_agent.cc @@ -782,10 +782,24 @@ void PasswordGenerationAgent::PasswordNoLongerGenerated() { // Clear all other password fields. for (WebInputElement& element : current_generation_item_->password_elements_) { + GenerationItemInfo* const item_before_dispatch = + current_generation_item_.get(); base::AutoReset auto_reset_update_confirmation_password( ¤t_generation_item_->updating_other_password_fields_, true); - if (current_generation_item_->generation_element_ != element) + if (current_generation_item_->generation_element_ != element) { element.SetAutofillValue(blink::WebString()); + // SECURITY: SetAutofillValue dispatches a synchronous focus event when + // |element| is unfocused. JS can call .focus() on another generation- + // eligible field, which on desktop synchronously re-enters + // ShowPasswordGenerationSuggestions -> MaybeCreateCurrentGenerationItem + // (via ShowVirtualKeyboardOnElementFocus -> FocusChangeComplete). Since + // password_is_generated_ was cleared above, the guard at line 808-810 + // does not hold and current_generation_item_ may be replaced, freeing + // the struct that |auto_reset_update_confirmation_password| points into. + // AutoReset::scoped_variable_ is RAW_PTR_EXCLUSION (not BRP-protected). + // Crash safely instead of letting the destructor write freed memory. + CHECK_EQ(item_before_dispatch, current_generation_item_.get()); + } } // The above call to SetAutofillValue() dispatches events that may change the // DOM. Therefore, any cached FormData may be outdated, and we must not use a