CVE-2026-11002
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forcomponents/autofill/core/browser/filling/form_autofill_history.cc |
modified | |
ifcomponents/autofill/core/browser/filling/form_filler.cc |
modified |
Files Changed
components/autofill/core/browser/filling/form_autofill_history.cccomponents/autofill/core/browser/filling/form_autofill_history.hcomponents/autofill/core/browser/filling/form_filler.cc
Patch
From c7c2ce4068f1ddf78b2c019a7d4fec6a0a8800ea Mon Sep 17 00:00:00 2001
From: Gianmarco Picarella <picarella@google.com>
Date: Tue, 14 Apr 2026 07:57:24 -0700
Subject: [PATCH] Fix use-after-free of std::list iterator in FormFiller::UndoAutofill
Fixes a use-after-free crash in FormFiller::UndoAutofill(…) when
handling forms containing fields with duplicate FieldGlobalId.
Currently, FormAutofillHistory silently collapses these duplicate IDs
into a single entry. However, FormFiller::UndoAutofill(…) iterates over
all fields in the FormData to erase them. Calling
EraseFieldFillingEntry(…) for the duplicate IDs frees the underlying
std::list node prematurely. Subsequent loop iterations then attempt to
access this freed memory using a stale iterator, resulting in a browser
process crash.
This change resolved the problem by calling EraseFieldFillingEntry(…) on
the subset of unique FieldGlobalIds in a separate for loop. We also
fixed a minor bug in FormAutofillHistory::EraseFieldFillingEntry(…) by
updating size_ appropriately.
Bug: 494740162
Change-Id: Ic95dee48fa3ef8d2e3688880a3e49a6a37e4442b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7743400
Reviewed-by: Christoph Schwering <schwering@google.com>
Commit-Queue: Gianmarco Picarella <picarella@google.com>
Reviewed-by: Jihad Hanna <jihadghanna@google.com>
Cr-Commit-Position: refs/heads/main@{#1614452}
---
diff --git a/components/autofill/core/browser/filling/form_autofill_history.cc b/components/autofill/core/browser/filling/form_autofill_history.cc
index 9d1e6c7..0e4e771f 100644
--- a/components/autofill/core/browser/filling/form_autofill_history.cc
+++ b/components/autofill/core/browser/filling/form_autofill_history.cc
@@ -95,12 +95,14 @@
}
}
-void FormAutofillHistory::EraseFieldFillingEntry(
- std::list<FormFillingEntry>::iterator fill_operation,
- FieldGlobalId field_id) {
- fill_operation->erase(field_id);
- if (fill_operation->empty()) {
- EraseFormFillEntry(fill_operation);
+void FormAutofillHistory::EraseFieldFillingEntries(
+ std::list<FormFillingEntry>::iterator filling_entry,
+ base::span<const FieldGlobalId> field_ids) {
+ for (const FieldGlobalId& field_id : field_ids) {
+ size_ -= filling_entry->erase(field_id);
+ }
+ if (filling_entry->empty()) {
+ EraseFormFillEntry(filling_entry);
}
}
diff --git a/components/autofill/core/browser/filling/form_autofill_history.h b/components/autofill/core/browser/filling/form_autofill_history.h
index e60e0e32..4199dc4c0 100644
--- a/components/autofill/core/browser/filling/form_autofill_history.h
+++ b/components/autofill/core/browser/filling/form_autofill_history.h
@@ -103,12 +103,12 @@
FillingProduct filling_product,
bool is_refill);
- // Erases the field history information corresponding to `field_id` in
- // `fill_operation`. If the form filling entry becomes empty afterwards, the
- // function also removes it from `history_`.
- void EraseFieldFillingEntry(
- std::list<FormFillingEntry>::iterator fill_operation,
- FieldGlobalId field_id);
+ // Erases the field history information corresponding to all `field_ids` from
+ // `filling_entry`. If `filling_entry` becomes empty afterwards, the function
+ // also removes it from `history_`.
+ void EraseFieldFillingEntries(
+ std::list<FormFillingEntry>::iterator filling_entry,
+ base::span<const FieldGlobalId> field_ids);
// Returns the first entry in `history_` (corresponding to the last
// chronological entry) that has information about the field represented by
diff --git a/components/autofill/core/browser/filling/form_filler.cc b/components/autofill/core/browser/filling/form_filler.cc
index 9fcd29a2..5d1ed98 100644
--- a/components/autofill/core/browser/filling/form_filler.cc
+++ b/components/autofill/core/browser/filling/form_filler.cc
@@ -13,6 +13,7 @@
#include "base/check_op.h"
#include "base/containers/flat_set.h"
#include "base/containers/map_util.h"
+#include "base/containers/to_vector.h"
#include "base/feature_list.h"
#include "base/hash/hash.h"
#include "base/metrics/histogram_functions.h"
@@ -827,15 +828,18 @@
previous_state.autofill_source_profile_guid);
autofill_field.set_autofilled_type(previous_state.autofilled_type);
autofill_field.set_filling_product(previous_state.filling_product);
-
- // The filling history is not cleared on previews as it might be used for
- // future previews or for the filling. it is also cleared field by field
- // because some fields in the current entry might not be used now but
- // could still be valuable (see crbug.com/416019464).
- form_autofill_history_.EraseFieldFillingEntry(fill_operation_it,
- field.global_id());
}
}
+
+ if (action_persistence == mojom::ActionPersistence::kFill) {
+ // The filling history is not cleared on previews as it might be used for
+ // future previews or for the filling. It is also cleared field by field
+ // because some fields in the current entry might not be used now but
+ // could still be valuable (see crbug.com/416019464).
+ form_autofill_history_.EraseFieldFillingEntries(
+ fill_operation_it, base::ToVector(fields, &FormFieldData::global_id));
+ }
+
form.set_fields(std::move(fields));
// Do not attempt a refill after an Undo operation.
Original Bug Report
Use-after-free of std::list iterator in FormFiller::UndoAutofill via duplicate FieldGlobalIds
Use-after-free of std::list iterator in FormFiller::UndoAutofill via duplicate FieldGlobalIds
Summary
A use-after-free in the browser process can be triggered by a compromised renderer that sends a FormData containing duplicate FieldRendererIds through the mojom::AutofillDriver::AskForValuesToFill IPC. When the user fills a form with Autofill and subsequently undoes the fill, the browser’s FormFiller::UndoAutofill iterates over all fields including duplicates, erasing fill history entries one by one. Because duplicates were silently collapsed into a single map entry during the fill, the loop drains the map and frees the underlying std::list node while a stale iterator to that node remains in use for subsequent iterations. The dangling iterator is not protected by MiraclePtr since the freed object is an STL list node, not a raw_ptr-wrapped member. This affects all platforms.
Bisect
Introducing Commit: a58a578316bfa8edc0dcc649581e9a394509964d
- Date: 2025-05-07
- Author: Jihad Hanna <jihadghanna@google.com>
- Review: https://chromium-review.googlesource.com/c/chromium/src/+/6515317
This CL changed UndoAutofill from erasing the entire fill history entry once after the loop to erasing entries per-field inside the loop (to fix crbug.com/416019464). The new EraseFieldFillingEntry deletes the std::list node when the map empties, but the loop still holds fill_operation_it — so duplicate field IDs from a compromised renderer can drain the map early and free the node mid-iteration.
Root Cause
The browser process does not enforce that FieldRendererId values are unique within a single FormData. The FormData::fields() accessor documents this explicitly, noting that collisions can occur when the renderer is compromised:
// components/autofill/core/common/form_data.h:295-310
//
// WARNING: `fields` may contain duplicates:
//
// Usually, FormFieldData::global_id() ... uniquely identify
// objects in `fields`. This is reliable enough for practical purposes, but
// not guaranteed.
//
// Collisions are possible in rare cases. Two known scenarios are:
// - The renderer is compromised and sends duplicates.
Neither IsValidFormData() nor bad_message::CheckFieldInForm() reject a FormData with duplicate field IDs. IsValidFormData() checks only string lengths and field count; CheckFieldInForm() checks only whether a given field_id exists somewhere in the form.
When a form is filled through Autofill, FormAutofillHistory::AddFormFillingEntry records per-field undo state in a std::map<FieldGlobalId, FieldFillingEntry> within a std::list node. Because std::map::emplace does not overwrite existing entries, duplicate FieldGlobalId values in the input are silently collapsed into a single map key:
// components/autofill/core/browser/filling/form_autofill_history.cc:64-90
for (const auto [field, autofill_field] :
base::zip(filled_fields, filled_autofill_fields)) {
size_ +=
history_.front()
.emplace(field->global_id(), FieldFillingEntry(...))
.second; // returns false (no increment) for duplicate keys
}
The asymmetry emerges during undo. FormFiller::UndoAutofill retrieves a single fill_operation_it pointing to the list node, then iterates over all fields in the FormData, including every duplicate:
// components/autofill/core/browser/filling/form_filler.cc:782-821
for (FormFieldData& field : fields) {
auto it = fill_operation_it->find(field.global_id());
CHECK(it != fill_operation_it->end());
const FormAutofillHistory::FieldFillingEntry& previous_state = it->second;
// ...
if (action_persistence == mojom::ActionPersistence::kFill) {
// ...
form_autofill_history_.EraseFieldFillingEntry(fill_operation_it,
field.global_id());
}
}
Each call to EraseFieldFillingEntry removes one key from the map. When the map becomes empty, it frees the entire list node:
// components/autofill/core/browser/filling/form_autofill_history.cc:98-105
void FormAutofillHistory::EraseFieldFillingEntry(
std::list<FormFillingEntry>::iterator fill_operation,
FieldGlobalId field_id) {
fill_operation->erase(field_id);
if (fill_operation->empty()) {
EraseFormFillEntry(fill_operation); // history_.erase(fill_operation)
}
}
With N unique fields and 2N total fields (each duplicated), the first N iterations of the loop erase all unique keys, draining the map to zero entries and freeing the list node. On iteration N+1, the code dereferences fill_operation_it to call find() on the freed map, producing a heap-use-after-free in the browser process’s main thread.
This UAF is not mitigated by any existing defense. MiraclePtr does not apply because the dangling reference is a standard library list iterator, not a raw_ptr<> class member. The CHECK(it != fill_operation_it->end()) guard executes after the UAF at fill_operation_it->find(), so it cannot prevent the invalid access.
Reproduce
Tested at commit 7c89d33808e551aed6122c1f324864784011c158.
Apply the renderer-only patch (simulates a compromised renderer by duplicating all fields in ExtractFormData):
cd ~/chromium/src
git apply patch.diff
autoninja -C out/asan-release chrome
Start an HTTP server and launch the ASAN build:
python3 -m http.server 8888 &
out/asan-release/Chromium.app/Contents/MacOS/Chromium --user-data-dir=/tmp/poc-$(date +%s)
Trigger manually:
- Navigate to
chrome://settings/addresses, click “Add”, fill in any address, and save. - Navigate back to
http://127.0.0.1:8888/poc.html. - Click the first form field (name field) and select the autofill address suggestion to fill the form.
- Click the first field (name field) again — it must be the first/leftmost field for the “Undo Autofill” option to appear.
- Select “Undo Autofill” from the dropdown.
The browser process crashes with the following ASAN report:
==3727567==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b485150ee68 at pc 0x56328503622a bp 0x7ffe4ca09270 sp 0x7ffe4ca09268
READ of size 8 at 0x7b485150ee68 thread T0 (chrome)
#0 in autofill::FormFiller::UndoAutofill(...) gen/third_party/libc++/src/include/__tree:950:54
#1 in autofill::BrowserAutofillManager::UndoAutofill(...) browser_autofill_manager.cc:2167
#2 in autofill::BrowserAutofillManager::OnAskForValuesToFillImpl(...) browser_autofill_manager.cc:1273
...
#11 in autofill::mojom::AutofillDriverStubDispatch::Accept(...) autofill_driver.mojom.cc:1899
freed by thread T0 (chrome) here:
#0 in operator delete
#1 in std::list<...>::erase(...)
#2 in autofill::FormAutofillHistory::EraseFieldFillingEntry(...) form_autofill_history.cc:110
#3 in autofill::FormFiller::UndoAutofill(...) form_filler.cc:818
#4 in autofill::BrowserAutofillManager::UndoAutofill(...) browser_autofill_manager.cc:2167
#5 in autofill::BrowserAutofillManager::OnAskForValuesToFillImpl(...) browser_autofill_manager.cc:1273
SUMMARY: AddressSanitizer: heap-use-after-free gen/third_party/libc++/src/include/__tree:950:54 in autofill::FormFiller::UndoAutofill(...)
The complete ASAN log is in asan.log.
Credit
Please use c6eed09fc8b174b0f3eebedcceb1e792 as the credit for this vulnerability. Thank you.