CVE-2026-14009
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/autofill/content/renderer/password_autofill_agent.cc |
modified |
Files Changed
components/autofill/content/renderer/password_autofill_agent.cc
Patch
From 2a8aa60275ec21a9db85e25bed582697aeea9dd8 Mon Sep 17 00:00:00 2001
From: Maria Kazinova <kazinova@google.com>
Date: Mon, 01 Jun 2026 05:20:56 -0700
Subject: [PATCH] [Passwords] Prevent crashes on detached frame in PasswordAutofillAgent
Description: This CL adds checks to ensure render_frame() is valid
before accessing it in PasswordAutofillAgent. During filling
operations, script execution can cause the frame to be detached. If
destruction of the agent is delayed (e.g., during nested loops like
printing), subsequent accesses to the detached frame can cause crashes.
NO_IFTTT=Crash fix on non-iOS, no need to update iOS code.
Fixed: 516819850
Change-Id: I7cf1b25f90b42e6601e9099768c1bedab3773e88
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7883070
Commit-Queue: Maria Kazinova <kazinova@google.com>
Reviewed-by: Ioana Treib <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1639311}
---
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index 41594d4..d04663d4c 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -759,6 +759,9 @@
if (element && !element.SuggestedValue().IsEmpty()) {
element.SetAutofillValue(element.SuggestedValue());
}
+ // WARNING: SetAutofillValue can trigger JS that detaches the frame.
+ // Do not add code here that assumes the frame is still valid without
+ // checking.
}
std::optional<PasswordSuggestionRequest>
@@ -776,6 +779,10 @@
void PasswordAutofillAgent::NotifyPasswordManagerAboutUserFieldModification(
const WebInputElement& element,
FieldModificationType modification_type) {
+ // No need to post a notification if the frame has already detached.
+ if (!render_frame()) {
+ return;
+ }
if (element.FormControlTypeForAutofill() == kInputPassword) {
auto iter = password_to_username_.find(FieldRef(element));
if ((iter != password_to_username_.end()) &&
@@ -1045,6 +1052,11 @@
FieldPropertiesMask field_properties) {
CHECK(input);
input.SetAutofillValue(WebString::FromUtf16(credential));
+ // No need to process changes if the frame has detached upon filling
+ // completion.
+ if (!render_frame()) {
+ return;
+ }
field_data_manager().UpdateFieldDataMap(form_util::GetFieldRendererId(input),
credential, field_properties);
@@ -1145,6 +1157,9 @@
const WebInputElement& user_input,
AutofillSuggestionTriggerSource trigger_source,
const SynchronousFormCache& form_cache) {
+ if (!render_frame()) {
+ return std::nullopt;
+ }
base::UmaHistogramEnumeration("PasswordManager.SuggestionPopupTriggerSource",
trigger_source);
// TODO(crbug.com/408843433): Don't extract the data here but pass it in from
@@ -1191,7 +1206,7 @@
DCHECK(username_element && password_element && password_info);
username_element->Reset();
password_element->Reset();
- if (!element) {
+ if (!element || !render_frame()) {
return false;
}
if (suggestion_banned_fields_.contains(GetFieldRendererId(element))) {
@@ -1301,6 +1316,9 @@
return;
checked_safe_browsing_reputation_ = true;
+ if (!render_frame()) {
+ return;
+ }
WebLocalFrame* frame = render_frame()->GetWebFrame();
GURL frame_url = GURL(frame->GetDocument().Url());
WebFormElement form_element = element.GetOwningFormForAutofill();
@@ -1312,6 +1330,9 @@
}
bool PasswordAutofillAgent::FrameCanAccessPasswordManager() {
+ if (!render_frame()) {
+ return false;
+ }
// about:blank or about:srcdoc frames should not be allowed to use password
// manager. See https://crbug.com/756587.
WebLocalFrame* frame = render_frame()->GetWebFrame();
@@ -1386,6 +1407,9 @@
logger->LogBoolean(Logger::STRING_ONLY_VISIBLE, only_visible);
}
+ if (!render_frame()) {
+ return;
+ }
WebLocalFrame* frame = render_frame()->GetWebFrame();
// Make sure that this security origin is allowed to use password manager.
@@ -1534,7 +1558,8 @@
}
bool PasswordAutofillAgent::IsPrerendering() const {
- return render_frame()->GetWebFrame()->GetDocument().IsPrerendering();
+ return render_frame() &&
+ render_frame()->GetWebFrame()->GetDocument().IsPrerendering();
}
bool PasswordAutofillAgent::IsUsernameInputField(
@@ -1553,6 +1578,9 @@
logger->LogMessage(Logger::STRING_DID_START_PROVISIONAL_LOAD_METHOD);
}
+ if (!render_frame()) {
+ return;
+ }
WebLocalFrame* navigated_frame = render_frame()->GetWebFrame();
if (navigated_frame->IsOutermostMainFrame()) {
// This is a new navigation, so require a new user gesture before filling in
@@ -2127,6 +2155,7 @@
// Lazily bind this interface.
if (!password_manager_driver_) {
+ CHECK(render_frame());
render_frame()->GetRemoteAssociatedInterfaces()->GetInterface(
&password_manager_driver_);
}
Original Bug Report
Potential Use-After-Free in PasswordAutofillAgent::DoFillField via nested print event loop
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential Use-After-Free (UAF) vulnerability exists in PasswordAutofillAgent due to synchronous DOM events dispatched inside DoFillField. By triggering a nested run loop (via window.print()) during a synchronous event listener and subsequently detaching the iframe, the PasswordAutofillAgent object can be destroyed while its methods remain on the call stack. Upon exiting the nested run loop, execution resumes inside a freed this context, leading to a Use-After-Free.
Affected files:
components/autofill/content/renderer/password_autofill_agent.cccomponents/autofill/content/renderer/password_autofill_agent.h
Estimated timestamp from git blame: 2024-02-27
Bug Analysis & Root Cause
In components/autofill/content/renderer/password_autofill_agent.cc, the function PasswordAutofillAgent::DoFillField is responsible for autofilling credentials into DOM input fields:
void PasswordAutofillAgent::DoFillField(WebInputElement input,
const std::u16string& credential,
FieldPropertiesMask field_properties) {
CHECK(input);
input.SetAutofillValue(WebString::FromUtf16(credential)); // <--- Synchronous DOM Event Dispatch
field_data_manager().UpdateFieldDataMap(...); // <--- Use-After-Free
...
TrackAutofilledElement(input);
}
Calling input.SetAutofillValue() triggers Blink to update the input’s value and synchronously dispatch input and change DOM events. A malicious page can register a synchronous event listener on the input field. When this listener is invoked, it can initiate frame teardown and immediately enter a nested run loop by invoking window.print().
Potential Exploitation Mechanism
An attacker could potentially trigger the Use-After-Free using the following sequence of events:
- A parent document (Origin A) embeds a cross-origin login iframe (Origin B).
- Origin B registers a synchronous
changeevent listener on its username input element. - The user interacts with the iframe and triggers Chrome’s password autofill.
PasswordAutofillAgent::DoFillFieldis called, which callsinput.SetAutofillValue(...).- Blink synchronously dispatches the
changeevent, executing the page’s event listener in Origin B. - The event listener calls
window.parent.postMessage('detach', '*')and immediately invokeswindow.print(). window.print()entersPrintRenderFrameHelper::ScriptedPrintand spins a nested message loop with nestable tasks allowed (base::RunLoop::Type::kNestableTasksAllowed).- The parent frame receives the postMessage and removes the iframe from the DOM.
- The browser process sends a
mojom::Frame::DeleteIPC to the child renderer, which is processed immediately within the nested run loop. RenderFrameImpl::Deletedetaches the frame, invokingOnDestruct()onAutofillAgent. This posts aDeleteSoontask to the default queue, which executes immediately inside the nested run loop.- When
AutofillAgentis destroyed, its ownedPasswordAutofillAgentis also destroyed. - Due to class-member destruction order in
AutofillAgent,FormTrackeris destroyed beforePasswordAutofillAgent, releasing anyraw_reforraw_ptrreferences to it and dropping the BackupRefPtr reference count to 0. This bypasses the BackupRefPtr quarantine, deallocating the memory immediately. - Once the print dialog is dismissed and the nested loop terminates, execution unwinds back to
DoFillField. - The call to
field_data_manager()is made on a freedthispointer, resulting in a Use-After-Free.
Note: Our tooling does not currently have the capability to run code or compile a working proof-of-concept, so these steps represent a potential and theoretical attack vector based on static code path analysis.
Suggested Remediation
To prevent this issue, PasswordAutofillAgent should be tracked using a base::WeakPtr to verify liveness after returning from operations that execute arbitrary page JavaScript. Alternatively, avoid calling field_data_manager() or making member accesses on this after SetAutofillValue without verifying if the frame observer or agent is still valid, or leverage a weak pointer pattern like base::WeakPtrFactory in PasswordAutofillAgent to exit gracefully if this has been destroyed.
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.