Open Bug 2043810 Opened 5 months ago Updated 1 day ago

PBackgroundSessionStorageCache accepts forged principals for cross-process sessionStorage reads

Categories

(Core :: Storage: localStorage & sessionStorage, defect, P3)

defect

Tracking

()

UNCONFIRMED

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)

Attached patch patch.diff (obsolete) — — 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.

Flags: sec-bounty?
Group: firefox-core-security → dom-core-security
Component: Security → Storage: localStorage & sessionStorage
Keywords: sec-want
Product: Firefox → Core
Duplicate of this bug: 2044595
Severity: -- → S3
Priority: -- → P3
Duplicate of this bug: 2045557
Duplicate of this bug: 2050500

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.

Attachment #9591825 - Attachment is obsolete: true
Flags: needinfo?(bugmail)
Flags: needinfo?(bugmail)
Duplicate of this bug: 2054636
Duplicate of this bug: 2055770
Duplicate of this bug: 2058337
Duplicate of this bug: 2060045
Duplicate of this bug: 2060076
Duplicate of this bug: 2062759
Duplicate of this bug: 2063128
Duplicate of this bug: 2065537
Duplicate of this bug: 2065826
Duplicate of this bug: 2066612
Duplicate of this bug: 2069202
Duplicate of this bug: 2069953
Duplicate of this bug: 2070154
Flags: sec-bounty? → sec-bounty-
Duplicate of this bug: 2072900
Duplicate of this bug: 2073953
Duplicate of this bug: 2073959
Duplicate of this bug: 2074145
Duplicate of this bug: 2075340
Duplicate of this bug: 2075367
Duplicate of this bug: 2076071
Duplicate of this bug: 2076143
Duplicate of this bug: 2076376
Duplicate of this bug: 2077356
Duplicate of this bug: 2077678
Duplicate of this bug: 2078522
Duplicate of this bug: 2080023
Duplicate of this bug: 2080425
Duplicate of this bug: 2080968

Given the number of duplicates and the sec-want rating I'm unhiding this bug.

Group: dom-core-security
Duplicate of this bug: 2080812

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.

  1. On the already-reported forged cache actor: RecvCheckpoint
    (StorageIPC.cpp:1387) -> UpdateData (SessionStorageManager.cpp:894)
    calls GetOriginRecord(..., 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.
  2. Separate manager-actor path: SessionStorageManagerParent::RecvClearStorages
    (StorageIPC.cpp:1452) accepts attacker-supplied OriginAttributesPattern,
    origin scope and matching mode and forwards them to ClearStorages ->
    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.
  3. topContextId is low-entropy: GenerateProcessSpecificId composes 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
    (RecvLoad returns 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.
  4. Scope of the gate asymmetry: among the five listed top-level
    manager-lifecycle handlers at BackgroundParentImpl.cpp:984-1062, all
    carry IsOtherProcessActor -> IPC_FAIL; the manager constructor has no
    such gate at either layer (dispatcher BackgroundParentImpl.cpp:448-455;
    second layer StorageIPC.cpp:1591-1595).
  5. GetOrCreate (SessionStorageManager.cpp:773) get-or-inserts into the
    static sManagers table; removal messages are parent-only
    (BackgroundParentImpl.cpp:984, :1015) and shutdown also clears the table,
    so entries remain referenced by sManagers until 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.

Duplicate of this bug: 2084650

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):

  1. 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.

  2. 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>.

  3. 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).

  4. 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.

Duplicate of this bug: 2089844
Duplicate of this bug: 2090709
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: