PBackgroundSessionStorageCache accepts forged principals for cross-process sessionStorage reads
Categories
(Core :: Storage: localStorage & sessionStorage, defect, P3)
Tracking
()
People
(Reporter: atsu120225, Unassigned)
References
(Blocks 1 open bug)
Details
(Keywords: csectype-site-isolation, reporter-external, sec-want, Whiteboard: [client-bounty-form])
Attachments
(1 file, 1 obsolete file)
|
11.20 KB,
patch
|
Details | Diff | Splinter Review |
A compromised content process can create a PBackgroundSessionStorageManager for a known top BrowsingContext id, then create PBackgroundSessionStorageCache with forged PrincipalInfo and originKey. In a cross-origin iframe run, an example.com content process read partitioned example.net sessionStorage through the parent even though DOM access was blocked and the victim frame was in a different content process. The patch validates the cache principal against the sender remote type and rejects originKey values not derived from that principal.
Updated•5 months ago
|
Updated•5 months ago
|
Updated•5 months ago
|
| Reporter | ||
Comment 4•4 months ago
|
||
Disclosure sink: SessionStorageCacheParent::RecvLoad (dom/storage/StorageIPC.cpp) derives the lookup origin-attributes suffix from the child-supplied mPrincipalInfo and passes the child-supplied mOriginKey straight into BackgroundSessionStorageManager::CopyDataToContentProcess, which returns originRecord->mCache->SerializeData() to the child. Both record selectors are attacker-controlled and never checked against the sending process. Pre-patch, SessionStorageManagerParent has no RecvPBackgroundSessionStorageCacheConstructor override, so AllocPBackgroundSessionStorageCacheParent stores (PrincipalInfo, originKey) verbatim.
PoC: testcase-sessionstorage-cache-forgery-poc.patch -- a single self-contained patch adding dom/storage/gtest/TestSessionStorageCacheForgery.cpp (4 tests) plus its moz.build wiring; all explanation lives in the file's comments. Built into xul-gtest and run on mozilla-central (./mach gtest 'SessionStorageCacheForgery.*' -> 0 warnings, 4/4 PASS). The tests exercise the exact predicates the fix relies on against the current tree: (1) the forged victim principal passes ValidatePrincipalCouldPotentiallyBeLoadedBy under a coarse "web" remote type; (2) it is rejected under the sender's real Fission remote type webIsolated=https://example.com but allowed for the legitimate webIsolated=https://example.org (the fix's parent->GetRemoteType() check); (3) GetStorageOriginKey re-derives the victim key gro.elpmaxe.:https:443 (the fix's originKey-recomputation check); (4) SerializeData() returns the victim's plaintext sessionStorage value, i.e. the exact bytes CopyDataToContentProcess hands back across the IPC boundary.
Updated•3 months ago
|
Updated•1 month ago
|
Comment 33•27 days ago
|
||
Given the number of duplicates and the sec-want rating I'm unhiding this bug.
Comment 35•21 days ago
|
||
A static source review of this chain on the git mirror revision 6411c7c917
(mozilla-firefox/firefox;
https://github.com/mozilla-firefox/firefox/commit/6411c7c9174104c4173a35749f26acdbf268b127),
re-checked against this thread on 2026-10-19, indicates the reported
boundary and adds a few facts I didn't find in the thread. This is source
analysis only: the added write, clear, enumeration and memory points below
were not runtime-tested; the runtime evidence on this bug (attachment
9602381) exercises the validation predicates and read-sink serialization,
not an end-to-end IPC chain.
Precondition throughout: a compromised content process able to send
malicious IPC. Ordinary web content cannot steer these selectors (the child
plumbing sends its own top BrowsingContext id and derives the principal
from the real document principal), so no pure-web reachability is claimed.
- On the already-reported forged cache actor:
RecvCheckpoint
(StorageIPC.cpp:1387) ->UpdateData(SessionStorageManager.cpp:894)
callsGetOriginRecord(..., true, ...)and applies the supplied
SSWriteInfos, i.e. creation/mutation of a parent-side origin record
under the forged selectors, not only reads. I have not demonstrated when
or whether such a mutation becomes visible to a victim page or is picked
up by SessionStore collection. - Separate manager-actor path:
SessionStorageManagerParent::RecvClearStorages
(StorageIPC.cpp:1452) accepts attacker-suppliedOriginAttributesPattern,
origin scope and matching mode and forwards them toClearStorages->
ClearStoragesInternal(SessionStorageManager.cpp:922). This is on the
manager actor claimed through the ungated constructor, not on the forged
cache actor; the clear-selection semantics and the effect on live child
caches are not established by the excerpts I reviewed. topContextIdis low-entropy:GenerateProcessSpecificIdcomposes ids
as(childID << 31) | seq(kIdTotalBits=53,kIdProcessBits=22;
nsContentUtils.cpp:13040-13059). My understanding is that top-level
BrowsingContexts are created in the parent process, where
XRE_GetChildID()returns 0, so candidate tab ids are small sequential
integers; if so, candidate ids are structured sequential values rather
than random. Actual search cost was not measured. A wrong guess fails
silently
(RecvLoadreturns an empty array), and an empty response does not
distinguish an unused id from an existing manager with no matching
record or legitimately empty storage.- Scope of the gate asymmetry: among the five listed top-level
manager-lifecycle handlers at BackgroundParentImpl.cpp:984-1062, all
carryIsOtherProcessActor -> IPC_FAIL; the manager constructor has no
such gate at either layer (dispatcher BackgroundParentImpl.cpp:448-455;
second layer StorageIPC.cpp:1591-1595). GetOrCreate(SessionStorageManager.cpp:773) get-or-inserts into the
staticsManagerstable; removal messages are parent-only
(BackgroundParentImpl.cpp:984, :1015) and shutdown also clears the table,
so entries remain referenced bysManagersuntil a matching parent
cleanup path or shutdown on the paths reviewed. I have not measured
per-entry size or practical memory impact.
For remediation: the constructor-validation approach already discussed here
(validate the cache principal against the sender's remote type and
re-derive originKey) prevents forged cache principal/origin-key selectors
from reaching RecvLoad and RecvCheckpoint; it does not
cover RecvClearStorages, which is a manager-actor operation and needs a
separate manager-level authorization check (for example ownership of the
claimed topContextId) or a check on the clear path. As design sketches only
(lifecycle not validated): the manager claim could be bound to the owning
ThreadsafeContentParentHandle owner set with Propagate re-binding on
reparenting, and the
child path could avoid get-or-insert to stop probe-created entries — though
that needs a demonstrated parent-side pre-creation path before replacing
GetOrCreate with Get. Happy to attach the full line-cited audit if useful.
Comment 37•16 days ago
|
||
Runtime confirmation of the full chain (compromised-content-process threat model, local build of central 6411c7c917, two tabs: attacker http://127.0.0.1:8471, victim http://127.0.0.2:8471):
-
The manager ctor is also ungated: PBackgroundSessionStorageManager(aTopContextId) lands in SessionStorageManagerParent -> BackgroundSessionStorageManager::GetOrCreate(aTopContextId) (dom/storage/SessionStorageManager.cpp:773) with no sender binding. aTopContextId is the top-level BrowsingContext::Id() the child would send (dom/storage/SessionStorageManager.cpp:351-352) - a small sequential integer. A brute claim of ids 1..60 from the attacker's content process found the victim tab (id 11; the attacker's own tab was 10), so no additional information beyond "another tab exists" is needed.
-
With the victim tab's topContextId and the victim's originKey (GetStorageOriginKey format = reversed-domain:scheme[:port], caps/BasePrincipal.cpp:1529), a forged PBackgroundSessionStorageCache + sync Load returned the victim tab's LIVE cross-site sessionStorage items, written seconds earlier through the real API: victimSecret=V-<marker> and victimSecret2=V2-<marker>.
-
Checkpoint (SSWriteInfo) on the same forged cache writes hostile data into the parent's authoritative per-tab copy - verified by a fresh forged cache actor whose Load returned pwnedKey=PWNED-<marker> alongside the victim's items. That copy is what CopyDataToContentProcess (dom/storage/StorageIPC.cpp:1356) serves to restored/duplicated/crash-recovered tabs, and UpdateData schedules a session-store update (dom/storage/SessionStorageManager.cpp:894-907).
-
Negative controls: a wrong originKey on the victim's manager returns 0 items, and the victim's originKey claimed on the attacker's OWN topContextId returns 0 items (the data is keyed per top-level context), so the hits above are specifically the cross-context read/write.
Honest boundary: the victim's already-loaded live document does not observe the hostile write (its child-side record is already mLoaded, so subsequent access is cloned locally) - the write manifests on restore/duplicate/crash-recovery paths and in session-store snapshots. The READ is immediate against live victim data.
Happy to share the harness pages and log if useful.
Description
•