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 <[email protected]> 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 <[email protected]> Commit-Queue: Gianmarco Picarella <[email protected]> Reviewed-by: Jihad Hanna <[email protected]> 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 <[email protected]>
- 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.