Cross-extension runtime.Port hijack via Conduits cast
Categories
(WebExtensions :: General, defect, P2)
Tracking
(firefox-esr115 wontfix, firefox-esr140154+ fixed, firefox-esr153154+ fixed, firefox152 wontfix, firefox153 wontfix, firefox154+ fixed)
People
(Reporter: vanirxxx, Assigned: zombie)
References
Details
(4 keywords, Whiteboard: [client-bounty-form][addons-jira][adv-main154+][adv-esr153.1+][adv-esr140.14+])
Attachments
(1 file, 4 external files, 1 obsolete file)
|
3.68 KB,
application/x-zip-compressed
|
Details |
Summary
A compromised content process can inject into and eavesdrop on another extension's privileged runtime.Port. The parent-side WebExtension message router ConduitsParent._cast (kind "port") selects delivery targets by portId alone, with none of the sender/extension verification its sibling "messenger" filter performs.
Affected
- Firefox Nightly 153.0a1, BuildID 20260612072730, changeset
c8c933829aec. - OS: All (logic bug, platform-independent; reproduced on Linux).
- Component: WebExtensions :: General (the Conduits IPC router).
Defect 1 — the "port" cast filter has no sender/extension verification
toolkit/components/extensions/ConduitsParent.sys.mjs, _cast():
let filters = {
// VULNERABLE: targets any endpoint whose portId matches — no verified, no extensionId.
port: remote =>
remote.portId === arg.portId &&
(arg.source == null || remote.source === arg.source),
// SIBLING (safe): messenger requires verified + same extension.
messenger: r =>
r.verified &&
r.id !== arg.sender.contextId &&
r.extensionId === arg.extensionId &&
r.recv.includes(method) && ...,
};
let targets = Array.from(Hub.remotes.values()).filter(filters[kind]);
let promises = targets.map(c => this._send(method, !!arg.query, c.id, arg));
A PortMessage from a compromised content process is therefore delivered to any port endpoint with a matching portId, regardless of which extension/principal owns it.
Defect 2 — the child-supplied portId is never validated at conduit open
ConduitsParent.sys.mjs recvConduitOpened → fillInAddress → verifyEnv: verifyEnv validates extensionId/principal/process, but never portId/source. So a compromised content process opens a conduit asserting an arbitrary (victim) portId.
Defect 3 — the portId secret leaks to content and is low-entropy
getUniqueId()=(uniqueProcessID & 0xffff) * 2**37 + (nextId++)(ExtensionUtils.sys.mjs): both factors are small dense counters, not nonces → live-portId candidate space ≈ low thousands, with a match-oracle (the silent cast routing +recvPortMessage/PortDisconnectfeedback).- Disclosure:
getSenderstampssender.contextId = source.id(an extension-processgetUniqueId) and ships it to content scripts via the unverified"tab"cast (tabfilter has noverifiedcheck);tabs.connect/sendMessagefrom a privileged background casts the actualportIdto the content script (recvPortConnect). Same per-process counter feedscontextIdandportId(cross-id reuse).
End-to-end chain
- Compromised content process (extension
attacker@poc) learns a victimcontextId/portIdfrom a deliveredsender/PortConnect(Defect 3), or brute-forces the low-entropy space with the routing oracle. - It opens a conduit asserting the victim
portId(Defect 2, no validation), and sendsPortMessage{portId: victim, source}—ProxyMessenger.recvPortMessage→castPortMessage("port", {portId, source}). _cast"port" filter (Defect 1) matches the victim's real port endpoint byportIdand delivers → the victim extension's privilegedruntime.Port.onMessagefires with attacker data; symmetrically the attacker registers on the channel and eavesdrops on the victim's messages.- Impact = content→privileged-extension message injection/eavesdrop: the background acts on messages it believes came from its own content script (privileged-API actions on the content script's behalf), and the attacker reads data the extension carries over the port. UXSS-class against the extension's principal.
PoC reproduction
poc/poc_conduit.js drives the real ConduitsParent module under xpcshell: registers a victim privileged port endpoint + a bystander in Hub.remotes, creates an attacker BroadcastConduit, and calls the real castPortMessage("port", {portId: victim}). Run: obj-asan-fuzz/dist/bin/xpcshell -g obj-asan-fuzz/dist/bin -a obj-asan-fuzz/dist/bin poc_conduit.js. See poc/RESULTS.md for the A/B output.
Empirical proof
Routing bug REPRODUCED on c8c933829aec (real module under xpcshell):
RUN 1 (as shipped): -> DELIVERED to [VICTIM privileged ext port] INJECTION SUCCEEDED
messenger sibling, same cross-extension cast: BLOCKED
RUN 2 (one-line fix: + remote.verified && remote.extensionId === arg.extensionId): injection BLOCKED
This confirms Defect 1 end-to-end at the routing layer and that the proposed fix closes it. The portId was supplied directly (standing in for Defect 3's separately-argued leak/brute-force, which is source-traced, not executed). No ASAN signature — auth/logic bug.
Proposed fix
Bring the "port" filter to parity with "messenger" — require the target endpoint be verified and belong to the sender's extension, and have the port cast carry the sender's extensionId:
port: remote =>
remote.portId === arg.portId &&
(arg.source == null || remote.source === arg.source) &&
remote.verified && remote.extensionId === arg.extensionId,
Defense-in-depth: validate/scope portId at recvConduitOpened (bind a port to the channel participants established at PortConnect), and stop shipping raw contextId/portId to content where not needed.
Updated•4 months ago
|
Comment 1•4 months ago
•
|
||
The non-secrecy of contextId is known (bug 1628178).
We should do a better job at strictly verifying whether IPC from/to a specific target is really allowed.
Although ports can originate from any process, once created it is pretty clear where the 1-n endpoints are.
Comment 2•4 months ago
|
||
Isn't a content script executed in the content process anyway and it's expected that a compromised content process can send messages that seem to come from the content script?
Comment 3•4 months ago
|
||
The rating assigned by our bug rating tool (currently under development) is:
Rating: sec-moderate
Explanation: Cross-extension runtime.Port hijack via Conduits cast missing extensionId/verified checks. The vulnerability is real and confirmed by code inspection. The _cast() port filter in ConduitsParent.sys.mjs only checks portId and optionally source, lacking the verified and extensionId checks that the messenger filter enforces. Combined with the fact that portId is not validated at conduit registration (recvConduitOpened/verifyEnv only checks extensionId and process, not portId) and that getUniqueId() produces low-entropy IDs, an attacker who registers a conduit asserting a victim's portId can inject messages into another extension's privileged background port. However, exploiting this requires a compromised content process (native code execution in the renderer) to craft IPC messages with arbitrary portIds — normal JavaScript (even from a malicious extension's content script) cannot directly control the portId assigned by the extension API layer (getUniqueId() is called internally). This is not reachable from normal browsing. The impact, while real — allowing cross-extension port message injection that could cause a victim extension to perform privileged actions based on attacker-controlled data — depends on what the victim extension does with port messages. The 'UXSS-class' characterization is overstated. The bug represents a meaningful IPC privilege escalation path from a compromised content process to another extension's background, which fits sec-moderate (a sec-high-class issue requiring an unusual prerequisite).
Please ni? me if you disagree with this rating.
Comment 4•4 months ago
|
||
Apparently, this is accessing ports which belong to other content processes.
Updated•4 months ago
|
Updated•4 months ago
|
| Assignee | ||
Comment 6•3 months ago
|
||
Updated•3 months ago
|
| Assignee | ||
Comment 8•3 months ago
|
||
Comment 9•3 months ago
|
||
Comment 12•3 months ago
|
||
firefox-esr140 Uplift Approval Request
- User impact if declined/Reason for urgency: A compromised process might be able to access messages from/to other extensions.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: medium
- Explanation of risk level: The fix is somewhat limited in scope, but not trivial. Worse case would be the fix disables some legitimate extensions from working, but it should be fairly obvious from the error logs if that happens.
- String changes made/needed?: Log only (new error logs added, which we don't localize).
- Is Android affected?: yes
| Assignee | ||
Comment 13•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D311434
Comment 14•3 months ago
|
||
firefox-esr153 Uplift Approval Request
- User impact if declined/Reason for urgency: A compromised process might be able to access messages from/to other extensions.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: medium
- Explanation of risk level: The fix is somewhat limited in scope, but not trivial. Worse case would be the fix disables some legitimate extensions from working, but it should be fairly obvious from the error logs if that happens.
- String changes made/needed?: Log only (new error logs added, which we don't localize).
- Is Android affected?: yes
| Assignee | ||
Comment 15•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D311434
Comment 16•3 months ago
|
||
firefox-release Uplift Approval Request
- User impact if declined/Reason for urgency: A compromised process might be able to access messages from/to other extensions.
- Code covered by automated testing?: yes
- Fix verified in Nightly?: yes
- Needs manual QE testing?: no
- Steps to reproduce for manual QE testing: N/A
- Risk associated with taking this patch: medium
- Explanation of risk level: The fix is somewhat limited in scope, but not trivial. Worse case would be the fix disables some legitimate extensions from working, but it should be fairly obvious from the error logs if that happens.
- String changes made/needed?: Log only (new error logs added, which we don't localize).
- Is Android affected?: yes
| Assignee | ||
Comment 17•3 months ago
|
||
Original Revision: https://phabricator.services.mozilla.com/D311434
Updated•3 months ago
|
Updated•3 months ago
|
Updated•3 months ago
|
Comment 18•3 months ago
|
||
| uplift | ||
Updated•3 months ago
|
Updated•3 months ago
|
Comment 19•3 months ago
|
||
| uplift | ||
| Assignee | ||
Updated•3 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Updated•2 months ago
|
Updated•11 days ago
|
Updated•6 days ago
|
Description
•