Closed Bug 2032631 Opened 4 months ago Closed 4 months ago

PSerialManager IPC bypasses dom.webserial.enabled pref and the Serial API permission flow

Categories

(Core :: DOM: Web Serial, defect)

defect

Tracking

()

RESOLVED FIXED
152 Branch
Tracking Status
firefox-esr115 --- unaffected
firefox-esr140 --- unaffected
firefox149 --- unaffected
firefox150 --- unaffected
firefox151 + fixed
firefox152 + fixed

People

(Reporter: pakhunov.anton.n, Assigned: gstoll)

References

(Regression)

Details

(5 keywords)

Attachments

(2 files, 1 obsolete file)

48 bytes, text/x-phabricator-request
Details | Review
48 bytes, text/x-phabricator-request
Details | Review

I was reviewing the pattern from bug 2032622 (DOMFullscreenParent trusting
child-supplied pref-gated state) and noticed Web Serial has a much sharper
version of the same problem. The pref dom.webserial.enabled defaults to
false on all release channels (StaticPrefList.yaml). The pref is the only
thing keeping Web Serial off in release builds.

Pref enforcement lives entirely on the child side:

dom/webidl/Serial.webidl:23
[SecureContext, Pref="dom.webserial.enabled", Exposed=(Window,DedicatedWorker)]

dom/webserial/Serial.cpp
Serial.requestPort / Serial.getPorts go through PortSecurityCheck and
the SerialPermissionRequest UI flow.

Both gates only apply to the WebIDL-bound JS API. Underneath that, the
PSerialManager protocol is exposed through PWindowGlobal:

dom/ipc/PWindowGlobal.ipdl:238
async PSerialManager();

The parent constructor in WindowGlobalParent has no pref check and no
permission check:

dom/ipc/WindowGlobalParent.cpp:1880-1890

already_AddRefed<PSerialManagerParent>
WindowGlobalParent::AllocPSerialManagerParent() {
return MakeAndAddRef<SerialManagerParent>();
}

mozilla::ipc::IPCResult WindowGlobalParent::RecvPSerialManagerConstructor(
PSerialManagerParent* aActor) {
auto* manager = static_cast<SerialManagerParent*>(aActor);
manager->Init(BrowsingContext()->GetBrowserId());
return IPC_OK();
}

Once the actor is up:

  • SerialManagerParent::RecvGetAvailablePorts calls
    platformService->EnumeratePorts and returns every serial device visible
    to the host. No permission check, no pref check. The platform service
    is a singleton that initializes unconditionally in
    SerialPlatformService::GetInstance regardless of the pref (it only
    consults the testing pref to choose between real and
    TestSerialPlatformService backends).

  • SerialManagerParent::RecvCreatePort instantiates a SerialPortParent
    for any portId the child names. There is no check that this
    (origin, portId) pair was ever permitted by the user.

  • SerialPortParent::RecvOpen opens the physical port through the
    platform service. The only validation is on bufferSize and the
    already-opened state; no permission verification.

Attack path:

  1. Attacker has code execution in a content process (separate sec-high
    precondition).
  2. From the compromised renderer, reach WindowGlobalChild and call
    wgc.sendPSerialManagerConstructor(child) directly. The WebIDL gate is
    skipped entirely. The pref is not checked parent-side.
  3. SendGetAvailablePorts. Attacker enumerates every USB-serial device
    the user has plugged in: vendorId, productId, port path. Useful for
    reconnaissance and for fingerprinting (some adapters expose serial
    numbers).
  4. Pick any portId from the list. SendCreatePort(portId, endpoint).
    SerialPortParent is constructed for that physical device.
  5. SendOpen(IPCSerialOptions{...}). Port is physically opened.
  6. The attacker now has arbitrary read and write access to whatever
    device is on that port: Arduino-class microcontrollers (flash and
    run firmware), industrial control devices, point-of-sale equipment,
    hardware security tokens that expose serial endpoints, embedded
    system debug ports, USB-to-serial cables connected to other
    machines, and so on.

The bypass takes out both layers of defense:
(a) the release-off dom.webserial.enabled pref that is intended to
keep the entire feature dormant on release / beta / ESR,
(b) the SerialPermissionRequest user-consent dialog flow that is
the web spec's entire security model for Web Serial.

Suggested fix, mirroring bug 2032622: gate at the parent IPC handler.

mozilla::ipc::IPCResult WindowGlobalParent::RecvPSerialManagerConstructor(
PSerialManagerParent* aActor) {
if (!StaticPrefs::dom_webserial_enabled()) {
return IPC_FAIL(this, "Web Serial disabled by pref");
}
auto* manager = static_cast<SerialManagerParent*>(aActor);
manager->Init(BrowsingContext()->GetBrowserId());
return IPC_OK();
}

That blocks actor creation when the pref is off. Per-port permission
enforcement is a separate fix that should land on top of this one:
SerialManagerParent::RecvCreatePort and SerialPortParent::RecvOpen
should look up the permission database for the (origin, portId) pair
the actor claims, instead of trusting the renderer to have already
asked the user.

Bugzilla dedupe: searched DOM: Device Interfaces (core-security and
public) and nine keyword combinations covering Web Serial pref bypass,
permission bypass, and SerialManagerParent topics. Zero prior hits on
this specific bypass. Bug 2031173 added an enterprise policy gate but
that gate is also pref-based and is bypassed by the same handler gap.
Bug 2030195 and 2029008 are about add-on permission gating, which is
a separate concern.

Verified on mozilla-central tip via hg-edge raw-file of
dom/ipc/WindowGlobalParent.cpp. AllocPSerialManagerParent and
RecvPSerialManagerConstructor are unchanged from the local clone
version: no pref or permission check is added.

Group: core-security → dom-core-security
Keywords: regression
Regressed by: 2010930

:gstoll, since you are the author of the regressor, bug 2010930, could you take a look? Also, could you set the severity field?

For more information, please visit BugBot documentation.

Flags: needinfo?(gstoll)

This sounds not great. I'm not sure how bad it is exactly to be able to start accessing devices, but I guess in the worst case it is like a sandbox escape.

[Tracking Requested - why for this release]: Possibly bad security regression. I think we should be sure to at least not ship the basic permission part here, as that is hopefully easy to fix. (The async nature of pref setting can make it annoying to get tests working sometimes.)

We can certainly fix the pref problem (although we're about to toggle this on for everyone in bug 2029625).

The other part is trickier. I looked into this during development, and IIRC ContentPermissionRequestBase (the base class of SerialPermissionRequest) is designed to be used from the content process. Although surely other subclasses of ContentPermissionRequestBase don't have this problem?

Flags: needinfo?(gstoll)
Blocks: webserial

The bug has a release status flag that shows some version of Firefox is affected, thus it will be considered confirmed.

Status: UNCONFIRMED → NEW
Ever confirmed: true

The bug is marked as tracked for firefox151 (nightly). However, the bug still isn't assigned.

:jstutte, could you please find an assignee for this tracked bug? Given that it is a regression and we know the cause, we could also simply backout the regressor. If you disagree with the tracking decision, please talk with the release managers.

For more information, please visit BugBot documentation.

Flags: needinfo?(jstutte)
Flags: needinfo?(jstutte) → needinfo?(gstoll)
Assignee: nobody → gstoll
Flags: needinfo?(gstoll)

OK, we can do this, we just have to implement nsIContentPermissionRequest instead of using the base class.

Attached file (secure)

Set release status flags based on info from the regressing bug 2010930

If you could get around the permission flow from web content this would be a sec-high privilege escalation, but it requires a compromised content process as a pre-condition which lowers the risk (but not the impact).

Status: NEW → ASSIGNED

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: sec bug that affects WebSerial in beta
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing: n/a
  • Risk associated with taking this patch: low
  • Explanation of risk level: reworking some IPC stuff, but we have pretty good test coverage around this
  • String changes made/needed?: no
  • Is Android affected?: no
Attachment #9574224 - Flags: approval-mozilla-beta?
Attached file (secure) (obsolete) —

dom/webserial/Serial.cpp:X:21: error: enumeration value 'EndGuard_' not handled in switch [-Werror,-Wswitch]

Flags: needinfo?(gstoll)
[task 2026-04-27T23:55:12.013+00:00] 23:55:12     INFO -  gmake[4]: Leaving directory '/builds/worker/workspace/obj-build/toolkit/library/build'
[task 2026-04-27T23:55:12.013+00:00] 23:55:12    ERROR -  ld.lld: error: undefined hidden symbol: mozilla::dom::SerialPermissionRequest::GetIgnoreAllowSitePermission(bool*)
[task 2026-04-27T23:55:12.013+00:00] 23:55:12     INFO -  >>> referenced by Unified_cpp_dom_webserial0.cpp
[task 2026-04-27T23:55:12.013+00:00] 23:55:12     INFO -  >>>               ../../../dom/webserial/Unified_cpp_dom_webserial0.o:(vtable for mozilla::dom::SerialPermissionRequest)
Flags: needinfo?(gstoll)

Backed out for causing build bustages at SerialManagerParent
Backout Link
Push with failures
Failure Log
Failure line /builds/worker/checkouts/gecko/dom/webserial/SerialManagerParent.cpp:X:10: error: incomplete type 'mozilla::dom::Serial' named in nested name specifier

Flags: needinfo?(gstoll)
Group: dom-core-security → core-security-release
Status: ASSIGNED → RESOLVED
Closed: 4 months ago
Flags: needinfo?(gstoll)
Resolution: --- → FIXED
Target Milestone: --- → 152 Branch
Attachment #9574224 - Attachment is obsolete: true
Attachment #9574224 - Flags: approval-mozilla-beta?

firefox-beta Uplift Approval Request

  • User impact if declined/Reason for urgency: sec bug that affects WebSerial in beta
  • Code covered by automated testing?: yes
  • Fix verified in Nightly?: no
  • Needs manual QE testing?: no
  • Steps to reproduce for manual QE testing: n/a
  • Risk associated with taking this patch: low
  • Explanation of risk level: reworking some IPC stuff, but we have pretty good test coverage around this
  • String changes made/needed?: no
  • Is Android affected?: no
Attachment #9574551 - Flags: approval-mozilla-beta?
Attached file (secure)
Attachment #9574551 - Flags: approval-mozilla-beta? → approval-mozilla-beta+
QA Whiteboard: [sec] [qa-triage-done-c152/b151]

Hi Daniel, the fix landed on 2026-04-28 with beta uplift on 2026-04-30. Any rough idea where this sits in the bounty review queue? No urgency, just trying to calibrate expectations. Thanks.

Flags: needinfo?(dveditz)

(In reply to pakhunov.anton.n from comment #29)

Hi Daniel, the fix landed on 2026-04-28 with beta uplift on 2026-04-30. Any rough idea where this sits in the bounty review queue? No urgency, just trying to calibrate expectations. Thanks.

Currently it sits nowhere because you did not submit it to our bounty program. I am doing so now for this bug but for the future please see https://www.mozilla.org/en-US/security/client-bug-bounty/#claiming-a-bounty

Please generally keep bug bounty related questions out of the bug and instead email us at the address in that document. The developers working on bugs are uninterested in bounty conversations and can't give any authoritative answers. Email us instead.

Flags: needinfo?(dveditz) → sec-bounty?
Component: DOM: Device Interfaces → Dom: Web Serial
Flags: sec-bounty? → sec-bounty+
Flags: sec-bounty+ → sec-bounty?

We don't believe this is eligible for a bounty. Yes, it gets around a permission check, but it requires a compromised process and isn't a sandbox-escape, and it also would require the victim to have a web serial device attached, and for that device to have a known vulnerability you can then exploit.

Flags: sec-bounty? → sec-bounty-
Group: core-security-release
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: