CVE-2026-8547
Overview
Background
- `PasswordManagerDriver`
- the per-frame object that mediates password-manager communication between the browser and a specific
RenderFrameHost; each frame (including subframes) has its own driver. - `PasswordGenerationPopupControllerImpl`
- the browser-side controller for the password-generation popup; it is bound to the frame/driver that opened it.
- `ContentPasswordManagerDriver::GetForRenderFrameHost(rfh)`
- resolves the driver for the frame that actually sent the current Mojo message.
- Mojo `RenderFrameHostReceiverSet`
- routes renderer IPC such that the target
rfhidentifies which frame the call came from.
Root Cause Analysis
The patched method in chrome_password_manager_client.cc (enclosing function name not shown in the @@ -1583,15 +1583,16 @@ hunk; it is the Mojo endpoint that updates the generation popup, per the commit subject) received a generated-password update over Mojo from some frame’s rfh, but then updated the generation popup based only on if (popup_controller_) — never checking that the popup actually belonged to the frame that sent the update. Any frame with a live popup_controller_ could therefore drive popup_controller_->UpdateGeneratedPassword(password_value) for a popup owned by a different frame.
The patch RELAXES nothing and TIGHTENS the guard: it computes driver (via ContentPasswordManagerDriver::GetForRenderFrameHost(rfh)) before the popup update and requires popup_controller_->driver().get() == driver before calling UpdateGeneratedPassword, so the update only applies when the popup’s own driver matches the calling frame’s driver. A new driver() accessor on PasswordGenerationPopupControllerImpl exposes the controller’s driver_ WeakPtr for that comparison. This closes the cross-frame mismatch while leaving the subsequent CHECK(driver) (the frame must be live) intact.
popup_controller_->driver().get() == driver equality check so only the owning frame can update it.Attack Path
- Open a generation popup
The user (or an attacker-influenced frame) causes a password-generation popup to appear, so
popup_controller_is live and bound to one frame’s driver. - Send an update from another frame
A different frame (e.g. a cross-origin subframe) sends a generated-password update over Mojo, arriving with its own
rfh. - Pre-patch unconditional update
The handler sees
popup_controller_is non-null and callsUpdateGeneratedPassword(password_value)without checking the driver, so the second frame’s value is applied to the first frame’s popup. - Policy bypass The frame that does not own the popup influences the generated password shown/populated for the owning frame, bypassing the per-frame enforcement the advisory describes as insufficient policy enforcement.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/password_manager/chrome_password_manager_client.cc |
modified |
Files Changed
chrome/browser/password_manager/chrome_password_manager_client.ccchrome/browser/ui/passwords/password_generation_popup_controller_impl.h
Audit Directions
- Shared UI, per-frame dataAudit browser-side singletons/controllers (popups, bubbles, infobars) updated from frame-scoped IPC; each update should verify the target frame/driver matches the UI’s owner.
- Driver/frame identity on Mojo endpointsFor handlers reached via
RenderFrameHostReceiverSet, confirm they compare the resolved driver forrfhagainst the object they mutate rather than assuming a single active frame. - Null-but-not-matching guardsFlag checks that only test existence (
if (controller_)) where identity is what matters; existence is not ownership.
Patch
From e3e793570c810d3ae89eb741c4afff9e999a43b7 Mon Sep 17 00:00:00 2001
From: Vasilii Sukhanov <vasilii@chromium.org>
Date: Mon, 30 Mar 2026 09:30:19 -0700
Subject: [PATCH] Check correctness of PasswordManagerDriver before updating the pop-up for password generation.
Fixed: 497632199
Change-Id: I39dbd2d076e17e938e98582850072265d55d3ec0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7705508
Commit-Queue: Vasilii Sukhanov <vasilii@chromium.org>
Reviewed-by: Anna Tsvirchkova <atsvirchkova@google.com>
Cr-Commit-Position: refs/heads/main@{#1607152}
---
diff --git a/chrome/browser/password_manager/chrome_password_manager_client.cc b/chrome/browser/password_manager/chrome_password_manager_client.cc
index d565e61..9d833d67 100644
--- a/chrome/browser/password_manager/chrome_password_manager_client.cc
+++ b/chrome/browser/password_manager/chrome_password_manager_client.cc
@@ -1583,15 +1583,16 @@
return;
}
+ PasswordManagerDriver* driver =
+ password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost(
+ rfh);
+
#if !BUILDFLAG(IS_ANDROID)
- if (popup_controller_) {
+ if (popup_controller_ && popup_controller_->driver().get() == driver) {
popup_controller_->UpdateGeneratedPassword(password_value);
}
#endif // !BUILDFLAG(IS_ANDROID)
- PasswordManagerDriver* driver =
- password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost(
- rfh);
// This method is called over Mojo via a RenderFrameHostReceiverSet; the
// current target frame must be live.
CHECK(driver);
diff --git a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
index 4245a5ad..6331192 100644
--- a/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
+++ b/chrome/browser/ui/passwords/password_generation_popup_controller_impl.h
@@ -121,6 +121,10 @@
void OnZoomChanged(
const zoom::ZoomController::ZoomChangedEventData& data) override;
+ base::WeakPtr<password_manager::PasswordManagerDriver> driver() const {
+ return driver_;
+ }
+
#if defined(UNIT_TEST)
PasswordGenerationPopupView* view() const { return view_; }
void SetViewForTesting(PasswordGenerationPopupView* view) { view_ = view; }
Original Bug Report
Potential Cross-Origin Password Generation Poisoning in ChromePasswordManagerClient
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A compromised renderer in a cross-origin iframe can potentially poison the shared password generation popup state for a victim origin. ChromePasswordManagerClient::PresaveGeneratedPassword fails to verify that the calling frame owns the active popup before updating its password value. If the user accepts the poisoned suggestion, the attacker-controlled password is saved for the victim’s account, leading to account takeover.
Affected files:
chrome/browser/password_manager/chrome_password_manager_client.ccchrome/browser/ui/passwords/password_generation_popup_controller_impl.cc
Estimated timestamp from git blame: 2024-05-24
Vulnerability Details
ChromePasswordManagerClient is a WebContentsUserData instance, meaning a single instance is shared across all frames (main frame and iframes) within a given tab. When a password generation popup is triggered, its state is managed by a PasswordGenerationPopupControllerImpl instance, which is stored in the ChromePasswordManagerClient::popup_controller_ member.
The vulnerability exists in the autofill.mojom.PasswordGenerationDriver::PresaveGeneratedPassword Mojo IPC handler. When this IPC is received, the method updates the popup_controller_ with the provided password without verifying that the sender frame is the actual owner of the currently active popup.
void ChromePasswordManagerClient::PresaveGeneratedPassword(
const autofill::FormData& form_data,
const std::u16string& password_value) {
content::RenderFrameHost* rfh =
password_generation_driver_receivers_.GetCurrentTargetFrame();
// ... checks ...
#if !BUILDFLAG(IS_ANDROID)
if (popup_controller_) {
// FLAW: No check is performed to ensure `rfh` owns the `popup_controller_`.
// A cross-origin iframe can overwrite the victim's generated password.
popup_controller_->UpdateGeneratedPassword(password_value);
}
#endif
// ...
}
Because the popup_controller_ correctly retains the victim frame’s driver_ and form_data_ from when the popup was initially created, accepting the poisoned password binds the attacker’s string to the victim’s origin. This bypasses Site Isolation and results in an account takeover.
Potential Attack Steps
(Note: These are suggested/potential steps based on code analysis; our tooling agent does not yet have the ability to run live code to provide a working proof of concept.)
- An attacker compromises a cross-origin iframe (e.g., via a v8 bug) embedded on a victim’s registration or password-change page.
- The user interacts with a password field on the victim site, prompting the browser to display a password generation popup.
- The victim’s frame legitimately creates the popup, and the browser stores its controller in the shared
ChromePasswordManagerClient::popup_controller_. - The compromised iframe observes the interaction and sends a
PresaveGeneratedPasswordMojo IPC to the browser, containing an attacker-known password. ChromePasswordManagerClient::PresaveGeneratedPasswordreceives the IPC from the attacker’s frame and unconditionally updates the sharedpopup_controller_with the attacker’s password.- The user clicks “Use suggested password” (or accepts it via keyboard), believing it to be a secure, browser-generated password for the victim site.
- The browser saves the attacker’s password under the victim’s origin and fills it into the form. The attacker can now log into the victim’s account remotely.
Suggested Fix
Before calling popup_controller_->UpdateGeneratedPassword(password_value), ChromePasswordManagerClient must verify that the incoming IPC originates from the same frame or driver that the popup_controller_ was instantiated for.
For example, the controller could expose its associated RenderFrameHost or PasswordManagerDriver, allowing ChromePasswordManagerClient to enforce an ownership check:
if (popup_controller_ &&
popup_controller_->driver().get() ==
password_manager::ContentPasswordManagerDriver::GetForRenderFrameHost(rfh)) {
popup_controller_->UpdateGeneratedPassword(password_value);
}
Evaluated with Chrome root at commit: 876d480da1f794d87813cfa2e6ff4fcf9771e939
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.