FormAutofill:GetRecords returns all stored PII to any content process — no origin filtering, no actor scope restriction (Fission IPC validation gap)
Categories
(Toolkit :: Form Autofill, defect)
Tracking
()
People
(Reporter: nguyentuanhung1149, Unassigned)
Details
(Keywords: reporter-external)
Summary
The FormAutofill JSWindowActor is registered without a matches constraint (browser/extensions/formautofill/api.js:143-156), making it available on every web page in every content process. The parent-side handler FormAutofill:GetRecords (toolkit/components/formautofill/FormAutofillParent.sys.mjs:315-317) calls collection.getAll() which returns ALL stored autofill records without any origin filtering. Additional handlers FormAutofill:RemoveAddresses and FormAutofill:RemoveCreditCards accept content-controlled GUIDs for record deletion.
A compromised content process can silently exfiltrate all stored autofill data (addresses with full PII, credit card metadata) and delete any stored records, without user interaction.
Affected Code (mozilla-central tip, verified 2026-10-05)
1. Actor registration — NO matches constraint
browser/extensions/formautofill/api.js:143-156
ChromeUtils.registerWindowActor("FormAutofill", {
parent: {
esModuleURI: "resource://autofill/FormAutofillParent.sys.mjs",
},
child: {
esModuleURI: "resource://autofill/FormAutofillChild.sys.mjs",
events: {
focusin: {},
"form-changed": { createActor: false },
"form-submission-detected": { createActor: false },
},
},
allFrames: true,
});
Compare to other actors that handle sensitive data:
- BlockedSite: matches: ["about:blocked?*"]
- NetError: matches: ["about:certerror?", "about:neterror?"]
The FormAutofill actor has NO matches restriction.
2. GetRecords returns ALL records without origin filtering
toolkit/components/formautofill/FormAutofillParent.sys.mjs:315-317
case "FormAutofill:GetRecords": {
const records = await this.getRecords(data);
return { records }; // ALL records returned to content process
}
toolkit/components/formautofill/FormAutofillParent.sys.mjs:752-799
async getRecords({ searchString, collectionName, fieldName }) {
collectionName ||= FormAutofillUtils.getCollectionNameFromFieldName(fieldName);
const collection = lazy.gFormAutofillStorage[collectionName];
const records = await collection.getAll(); // ALL records, NO origin filter
// ... filters by searchString only
return records;
}
3. Data exposed
Address records (full plaintext PII):
- name, organization, street-address, address-level1/2/3, postal-code, country, tel, email
Credit card records:
- cc-name, cc-number (masked: ************1234 — last 4 leaked), cc-exp-month, cc-exp-year, cc-type
- cc-number-encrypted (OS KeyStore encrypted blob — included in getAll response)
4. Content-controlled record deletion
toolkit/components/formautofill/FormAutofillParent.sys.mjs:376-387
case "FormAutofill:RemoveAddresses": {
data.guids.forEach(guid =>
lazy.gFormAutofillStorage.addresses.remove(guid)
);
}
case "FormAutofill:RemoveCreditCards": {
data.guids.forEach(guid =>
lazy.gFormAutofillStorage.creditCards.remove(guid)
);
}
Content-controlled GUIDs, no ownership validation.
5. Content-controlled record injection
FormAutofill:SaveAddress at line 355: adds addresses with content-controlled data (no auth).
Attack Chain
- Compromised renderer A (evil.com) in a Fission content process
- Actor instantiated (no matches restriction — any page can create it)
- Sends FormAutofill:GetRecords with {collectionName: "addresses"}
- Parent returns ALL stored addresses (names, addresses, phones, emails)
- Sends FormAutofill:GetRecords with {collectionName: "creditCards"}
- Parent returns ALL credit cards (names, last 4 digits, expiry, types, encrypted numbers)
- No user interaction. No visual indicator. Completely silent.
Comparison with LoginManagerParent (correct pattern)
LoginManagerParent.sys.mjs:293-297:
- Rejects content-provided origins
- Uses this.origin derived from this.manager.documentPrincipal
- Returns only logins matching current page origin
FormAutofillParent:
- Has formOrigin getter (line 405) using documentPrincipal (correct)
- But getRecords NEVER USES formOrigin — returns ALL records regardless
Impact
- Full PII leak: names, physical addresses, phone numbers, emails
- Credit card metadata: cardholder names, last 4 digits, expiry, card types
- Data destruction: all stored records can be deleted
- Data injection: fake addresses can be added
- Cross-origin: browser-global storage, not per-origin
- Silent: no user interaction or visual indicator
- Fission violation: site-isolated renderer accesses entire autofill database
Suggested Fix
- Add origin filtering to getRecords (use existing formOrigin getter)
- Add matches constraint or require user gesture for actor creation
- Add principal validation to Remove and Save handlers
- Never send cc-number-encrypted to content processes
Credit: Nguyen Tuan Hung (nguyentuanhung1149@gmail.com) VNU HCM - UIT
Comment 1•1 month ago
|
||
Please do not rate your own security bugs. Bugs must be rated by Mozilla.
| Reporter | ||
Updated•1 month ago
|
Comment 2•1 month ago
|
||
Automatic triage (under development): deduplication
No validation or verification has been performed.
This issue has been determined to be a duplicate of bug 2022419.
Both reports describe the same defect: FormAutofillParent.receiveMessage handles the test-only messages FormAutofill:GetRecords, FormAutofill:SaveAddress, FormAutofill:RemoveAddresses and FormAutofill:RemoveCreditCards for any content process. Because the FormAutofill actor is registered with allFrames: true and no matches restriction, a compromised content process can read every saved address and credit-card record through getRecords()/getAll(), add new records, and delete records by GUID. The attack chain, the affected handlers and the impact (silent PII exfiltration, record deletion and data injection) are the same.
Updated•1 month ago
|
Updated•1 month ago
|
Comment 3•1 month ago
|
||
General critique you should incorporate into future submissions:
What EXACT version are you reporting the bug against?
For a Mozilla-shipped binary we want the buildID (displayed in about:support). For source analysis we need the hash of the revision you are inspecting. A link is better because we have multiple repos you might be looking at. Your report says
Affected Code (mozilla-central tip, verified 2026-10-05)
- Actor registration — NO matches constraint
browser/extensions/formautofill/api.js:143-156
This is a complete fabrication. Or maybe Oct 5 is really the day you personally looked at it, but that tells us NOTHING about the version you were looking at. The snippet of code you quote is not at the lines you claim, and is missing a line of code that was added June 18. I don't know what version you were looking at, but I know it's at least 4 versions out of date.
There is no POC to verify your claims
Code that looks bad in a local analysis might be perfectly fine due to constraints elsewhere, or even unreachable. I'm not sure what you "verified" without one. In this case the code you identified will only be processed in the parent in a testing configuration.
You have requested a bounty In a couple of bugs. Please be aware that our client bug bounty program requires a POC that demonstrates that the vulnerability is reachable in Firerfox as shipped by Mozilla. Please read our client bounty program guidelines and FAQ on our web site.
Check for duplicates
This bug is a duplicate of one that was fixed in March (Firefox 150). A POC would have already saved you filing it, but the duplicate had been public for about 5 weeks when you filed this bug so you should have been able to find that as well.
Description
•