CVE-2026-117960
Overview
Background
- Pointer Lock API
element.requestPointerLock()hides the cursor and routes raw mouse movement to a page. Because it removes the visible cursor, Firefox shows a transient notification naming the origin that locked the pointer, so the user knows which site is in control.- `PointerLockParent` (JSWindowActor)
- The parent-process actor in
browser/actors/PointerLockParent.sys.mjsthat receives thePointerLock:Enteredmessage from the content process and drives the UI by callingbrowser.documentGlobal.PointerLock.entered(<origin>). - `WindowGlobalParent` / `documentPrincipal`
- Each frame (including cross-origin subframes) has its own
WindowGlobalParentwith adocumentPrincipal.this.manageris the actor’s window global, i.e. the frame that sent the message, which can be a subframe. - `browsingContext.top.currentWindowGlobal.documentPrincipal`
- The principal of the top-level document of the tab, regardless of which subframe triggered the action.
Root Cause Analysis
When handling PointerLock:Entered, the parent built the origin string shown in the pointer-lock notification from this.manager.documentPrincipal.originNoSuffix — the principal of the frame that requested the lock. Pointer lock can be requested from a cross-origin iframe, so this.manager may be a subframe window global whose origin differs from the top-level page. The notification therefore displayed the subframe’s origin as the site that locked the pointer, which an embedding attacker could exploit to make the warning name an arbitrary origin rather than the real controlling context.
The one-line fix replaces the source of the origin with this.manager.browsingContext.top.currentWindowGlobal.documentPrincipal.originNoSuffix, i.e. always the top-level document’s principal. After the patch the pointer-lock notification reflects the top-level origin the user actually sees in the address bar, closing the spoofing gap. The change touches only the string passed to PointerLock.entered; the lock mechanics are unchanged.
Attack Path
- Embed Attacker’s top-level page embeds a cross-origin iframe (or is itself embedded such that the requesting frame’s origin differs from the top), (inferred: standard web embedding).
- Request lock
Script in the subframe calls
requestPointerLock(); the content process sendsPointerLock:Entered. - Wrong origin resolved (pre-patch)
PointerLockParentreadsthis.manager.documentPrincipal.originNoSuffix— the subframe’s origin — and passes it toPointerLock.entered. - Misleading notification The pointer-lock warning displays the subframe origin, not the top-level origin the user associates with the tab.
- Primitive Origin spoofing in a security-relevant notification: the user is told a different site controls their pointer than the one in the address bar, which can be leveraged for UI deception while the cursor is hidden.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
switchbrowser/actors/PointerLockParent.sys.mjs |
modified |
Files Changed
browser/actors/PointerLockParent.sys.mjs
Audit Directions
- Other actor-side principal usesAudit other JSWindowActor parents that build user-facing security strings from
this.manager.documentPrincipal; many permission/notification UIs should use the top browsing context’s principal, not the sending frame’s. - Pointer-lock UI consistencyVerify every place the pointer-lock UI is updated (entered/exited/warning text) uses the same top-level origin source so they cannot disagree.
- Subframe-triggered prompts generallyReview fullscreen, autoplay, and similar notifications for the same requesting-frame-vs-top-frame origin confusion.
Patch
diff --git a/browser/actors/PointerLockParent.sys.mjs b/browser/actors/PointerLockParent.sys.mjs
index 97993953d71..8ee3f0e13af 100644
--- a/browser/actors/PointerLockParent.sys.mjs
+++ b/browser/actors/PointerLockParent.sys.mjs
@@ -8,7 +8,8 @@ export class PointerLockParent extends JSWindowActorParent {
switch (message.name) {
case "PointerLock:Entered": {
browser.documentGlobal.PointerLock.entered(
- this.manager.documentPrincipal.originNoSuffix
+ this.manager.browsingContext.top.currentWindowGlobal.documentPrincipal
+ .originNoSuffix
);
break;
}