CVE-2026-117976
Overview
Background
- `BrowsingContext`
- a cross-process object representing a browsing context (roughly a frame/tab). Its state is replicated between the content process(es) and the parent process via a synchronized set of fields defined by the
FIELD(...)macro list indocshell/base/BrowsingContext.h. - Synced field `CanSet` policy
- each replicated field declares who is allowed to set it:
CanSet::Unrestricted(any process, including a content/child process, may initiate the change),CanSet::ParentOnly(only the parent process), orCanSet::Custom(a custom validator). - `HistoryID`
- an
nsIDidentifying a browsing context’s session-history entry/identity. Session history and site-isolation decisions rely on it being trustworthy. - Site isolation / process model
- in Fission, a compromised content process must not be able to influence state that the parent treats as authoritative for security/navigation decisions.
Root Cause Analysis
The replicated BrowsingContext field HistoryID was declared FIELD(HistoryID, nsID, {.mCanSet = CanSet::Unrestricted}). Unrestricted means any process participating in the sync — including an untrusted content/child process — could set HistoryID on a browsing context. Because HistoryID identifies the history/navigation identity of the context, letting a content process set it lets that process desynchronize or spoof the history identity that the parent and other processes rely on, which is a site-isolation violation: a compromised or malicious renderer can assert control over cross-process navigation/history state it should not own.
The one-line patch changes the policy to FIELD(HistoryID, nsID, {.mCanSet = CanSet::ParentOnly}), so only the parent process may set HistoryID. Content processes attempting to set it are rejected by the sync machinery, restoring the invariant that this identity field is authoritative only when written by the trusted parent. This is purely an access-control/policy change on who may write a replicated field; the diff contains no memory-safety sink.
BrowsingContext field, HistoryID, was writable by any process (CanSet::Unrestricted); a content process could therefore set/spoof it. Tightening it to CanSet::ParentOnly makes the parent the sole authority for that field.Attack Path
- Compromise a content process The attacker runs code in a content/renderer process (e.g. via a prior renderer bug or simply a cooperating malicious page’s process). (precondition for a site-isolation bypass)
- Set the replicated field
From that process, initiate a sync change to
HistoryIDon a targetBrowsingContext; theUnrestrictedpolicy accepts it. - Desynchronize history identity
The spoofed/attacker-chosen
HistoryIDpropagates to the parent and other processes as the authoritative value. - Abuse navigation/history Use the mismatched/controlled identity to confuse site-isolation or session-history logic (e.g. cross-context history manipulation). (concrete downstream abuse inferred; the diff proves only that content could write the field)
- Primitive A content process influences cross-process history/identity state it should not control — a site-isolation (authorization) bypass.
Impact Assessment
BrowsingContext; the vulnerability is exploitable from a content process and is categorized by the advisory as a site-isolation issue (moderate). No privilege beyond running in a content process is required to attempt the field set. The gained capability is the ability to set/spoof the HistoryID identity field across processes; this is an authorization/policy weakness, not memory corruption — there is no memory-safety sink in the diff. The precise security impact of a spoofed HistoryID (navigation confusion, history spoofing) is plausible but the concrete exploitation chain is inferred beyond what the one-line policy change proves.Files Changed
docshell/base/BrowsingContext.h
Audit Directions
- `CanSet::Unrestricted` fieldsenumerate every
FIELD(..., {.mCanSet = CanSet::Unrestricted})inBrowsingContext.h(andWindowContext/SyncedContextequivalents) and ask whether a compromised content process writing it is safe; identity/security-relevant fields should beParentOnlyorCustom. - Fields feeding security decisionsspecifically review fields consumed by site-isolation, session history, and process-selection logic for over-permissive set policies.
- Custom setterscheck
CanSet::Customvalidators for fields likeBrowserIdto confirm they actually reject untrusted writers rather than only validating shape.
Patch
diff --git a/docshell/base/BrowsingContext.h b/docshell/base/BrowsingContext.h
index 0d669468347..5ad1e3b3d3e 100644
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -175,7 +175,7 @@ struct EmbedderColorSchemes {
* switched to another browser element this ID will remain the same but \
* hosted under the under the new browser element. */ \
FIELD(BrowserId, uint64_t, {.mCanSet = CanSet::Custom}) \
- FIELD(HistoryID, nsID, {.mCanSet = CanSet::Unrestricted}) \
+ FIELD(HistoryID, nsID, {.mCanSet = CanSet::ParentOnly}) \
FIELD(InRDMPane, bool, {.mTopOnly = true, .mCanSet = CanSet::ParentOnly}) \
FIELD(Loading, bool, {.mCanSet = CanSet::Unrestricted}) \
/* A field only set on top browsing contexts, which indicates that either: \