Closed Bug 2060310 Opened 4 days ago Closed 1 day ago

Bounce Tracking Protection: an iframe that navigates the top level becomes the extended navigation's initialHost and exempts itself from classification

Categories

(Core :: Privacy: Anti-Tracking, defect, P2)

defect

Tracking

()

RESOLVED FIXED
155 Branch
Tracking Status
firefox155 --- fixed

People

(Reporter: emz, Assigned: emz)

References

(Blocks 1 open bug)

Details

Attachments

(2 files)

A cross-site iframe can escape bounce tracker classification by nominating itself as the initialHost of the extended navigation it starts.

Steps to reproduce

  1. A.example embeds a cross-site iframe from B.example.
  2. The frame navigates the top level to B.example (<a target="_top">, <form target="_top">, or window.top.location = ...).
  3. B.example immediately server-redirects the top level back to A.example.

Actual result: B.example is never classified. It got a top-level, first-party context - so Set-Cookie on the 302 lands in its unpartitioned jar, readable on every subsequent bounce from any embedder - and the user is back on A.example none the wiser.

Expected result: B.example is classified as a bounce tracker candidate.

Why it happens

BounceTrackingState::OnStartNavigation derives the record's initialHost from nsILoadInfo::triggeringPrincipal (BounceTrackingState.cpp:684, written at :702 and :725). For a navigation initiated by a nested navigable, that is the frame's principal. BounceTrackingProtection::RecordStatefulBounces then skips any bounce-set host equal to initialHost (BounceTrackingProtection.cpp:337), so the frame's site is skipped, and the embedder is skipped as finalHost. Nothing is left to classify. MOZ_LOG:

OnStartNavigation: new BounceTrackingRecord(): {mInitialHost:example.net, ...}
RecordStatefulBounces: Skip host == initialHost: example.net
RecordStatefulBounces: Skip host == finalHost: example.com

No user gesture is required. The !mBounceTrackingRecord branch at :700-702 sets initialHost unconditionally - the hasUserActivation test at :703 only gates AddUserActivationHost - and the record is nulled 10s after every document load (privacy.bounceTrackingProtection.clientBounceDetectionTimerPeriodMS). A hidden iframe scripting top.location is enough.

initialHost is the only frame-derived field in the record. finalHost comes from the committed top-level document, and user activation is attributed to the top level (WindowGlobalParent::RecvRecordUserActivationForBTP uses TopWindowContext()->DocumentPrincipal()). So the same click that is denied B the activation exemption grants it a functionally identical initialHost exemption.

Spec status

Gecko is faithful here: process navigation start step 3 reads "Let |origin| be |sourceDocument|'s origin", and HTML supplies the iframe's document as sourceDocument for both trigger shapes. But record a user activation in the same document deliberately climbs to "|navigable|'s top-level traversable's active document". The sourceDocument wording exists to resolve privacycg/nav-tracking-mitigations#50, whose resolution was "set the initial host ... to the site which the popup was opened from" - i.e. the site the user was on and deliberately left. sourceDocument is a proxy for that which breaks for a nested initiator, so #50's bypass reappears through a different door.

Chromium is not affected. content/browser/btm/btm_bounce_detector.cc:705-711 takes the chain start from delegate_->GetLastCommittedURL(), consulting navigation_handle->GetInitiator() only when the tab has nothing committed, and :1044-1050 ignores subframe activations outright. The current spec text describes neither engine's safe behaviour.

Proposed fix

Derive initialHost from the top-level document the extended navigation started from: this tab's current (pre-navigation) top-level document, falling back to the initiator's top-level document only when the tab has nothing committed (window.open / target=_blank, preserving bug 1843308 / privacycg#50). Gate on the triggering principal being track-worthy so chrome-initiated navigations keep their empty initialHost and gain no new exemption.

Follow-ups, not in scope here

  • mUserActivationHosts still receives the frame's site, so a busting frame can still earn itself a dFPI storage-access grant via DynamicFpiNavigationHeuristic.cpp:151.
  • window.open from a cross-site iframe is the same gap through the opener chain.
  • bug 1926384 - finalHost self-nomination via window.close().
Assignee: nobody → emz
Status: NEW → ASSIGNED
See Also: → 2060555

The bounce tracking record's initial host came from the navigation's triggering
principal, which for a navigation started by a nested navigable is the frame's own
principal. RecordStatefulBounces exempts the initial host, so a cross-site iframe
could navigate the top level through its own origin and back to the embedder without
ever being classified, gaining a first party context to write unpartitioned cookies
from. The spec derives it from the navigation's source document, which is where the
gap comes from; a spec issue will be filed separately.

Use the site the context is leaving instead: the most recent non-initial document
committed in it, via BrowsingContext::GetHasLoadedNonInitialDocument and the current
window context, both updated at commit. A context with no committed document of its
own was opened by this navigation, so its opener is used.

The derivation no longer needs the navigation to have an initiator, so one started by
the browser also exempts the site it is leaving. That fixes bug 2060555.

This matches how user activation is already attributed:
WindowGlobalParent::RecvRecordUserActivationForBTP keys on the top window context, so
a click in a frame is credited to the embedding site. The record's user activation set
deliberately still keeps the triggering principal's site, because it feeds the dynamic
FPI storage access heuristic, which wants the site that was interacted with.

See Also: → 2061161
See Also: → 2061167
See Also: → 2061168
See Also: → 2061169
Pushed by ezuehlcke@mozilla.com: https://github.com/mozilla-firefox/firefox/commit/1b7232980c3d https://hg.mozilla.org/integration/autoland/rev/1d8bf3c7775e Derive the BTP extended navigation's initial host from the site being navigated away from r=timhuang https://github.com/mozilla-firefox/firefox/commit/514fc74dd92e https://hg.mozilla.org/integration/autoland/rev/7bdd7fc3d925 Test initial host derivation for frame and cross context initiated navigations r=timhuang
Status: ASSIGNED → RESOLVED
Closed: 1 day ago
Resolution: --- → FIXED
Target Milestone: --- → 155 Branch
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: