Bug 1830070: Correctly apply RFP Checks to about: documents and deal with pop-ups r=smaug,necko-reviewers,emilio

This patch has three parts to it:

1) Use NS_IsContentAccessibleAboutURI to ensure that only safe
   about: documents get exempted.

   With this change, we will no longer allow about:blank or
   about:srcdoc to be exempted base on URI.  If they are to be
   exempted, it will need to be base on other information.

2) In Document::RecomputeResistFingerprinting we previously
   deferred to a Parent Document if we had one, and either the
   principals matched or we were a null principal.

   We will do the same thing, except we will also defer to our
   opener as well as the parent document.  Now about:blank
   documents can be exempted.

   However, this deferral only works if the opener is
   same-process. For cross-process openers, we make the decision
   ourselves.

We can make the wrong decision though. CookieJarSettings is
inherited through iframes but it is _not_ inherited through popups.
(Yet. There's some discussion there, but it's not implemented.)

Conceptually; however, we do want CJS to inherit, and we do want
RFP to inherit as well.  Because a popup can collude with its
opener to bypass RFP and Storage restrictions, we should propagate
the CJS information.

This does lead to an unusual situation: if you have exempted
b.com, and a.com (which is not exempted) creates a popup for b.com
then that popup will not be exempted.  But an open tab for b.com
would be.  And it might be hard to tell those two apart, or why
they behave differently.

The third part of the patch:

3) In LoadInfo we want to populate information down from the
   opener to the popup.  This is needed because otherwise a
   cross-origin popup will not defer to its opener (because in
   Fission they're in different processes) and will decide if
   it should be exempted itself. It's the CookieJarSettings
   object that prevents the cross-origin document from thinking
   it should be exempted - CJS tells it 'No, you're a child
   (either a subdocument or a popup) and if I say you don't get
   an exemption, you don't.'


Finally, there is one more caveat: we can only defer to a parent
document or opener if it still exists.  A popup may outlive its
opener. If that happens, and something induces a call to
RecomputeResistFingerprinting, then (e.g.) an about:blank popup
may lose an RFP exemption that it had received from its parent.
This isn't expected to happen in practice -
RecomputeResistFingerprinting is only called on document creation
and pref changes I believe.

It is not possible for a popup to _gain_ an exemption though,
because even if the parent document is gone, the CJS lives on and
restricts it.

Differential Revision: https://phabricator.services.mozilla.com/D178866
This commit is contained in:
Tom Ritter 2023-06-26 17:04:40 +00:00
parent 9cac1faf76
commit 78fae782d0
8 changed files with 97 additions and 29 deletions

View File

@ -59,4 +59,4 @@ add_task(testA.bind(null, uri, testHWConcurrency, expectedResults));
// (E) RFP is not exempted on the popup maker
expectedResults = structuredClone(allSpoofed);
add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));
add_task(testE.bind(null, uri, testHWConcurrency, expectedResults));

View File

@ -1107,6 +1107,24 @@ BasePrincipal::IsURIInList(const nsACString& aList, bool* aResult) {
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::IsContentAccessibleAboutURI(bool* aResult) {
*aResult = false;
nsCOMPtr<nsIURI> prinURI;
nsresult rv = GetURI(getter_AddRefs(prinURI));
if (NS_FAILED(rv) || !prinURI) {
return NS_OK;
}
if(!prinURI->SchemeIs("about")) {
return NS_OK;
}
*aResult = NS_IsContentAccessibleAboutURI(prinURI);
return NS_OK;
}
NS_IMETHODIMP
BasePrincipal::GetIsOriginPotentiallyTrustworthy(bool* aResult) {
AssertIsOnMainThread();

View File

@ -137,6 +137,7 @@ class BasePrincipal : public nsJSPrincipals {
NS_IMETHOD SchemeIs(const char* aScheme, bool* aResult) override;
NS_IMETHOD IsURIInPrefList(const char* aPref, bool* aResult) override;
NS_IMETHOD IsURIInList(const nsACString& aList, bool* aResult) override;
NS_IMETHOD IsContentAccessibleAboutURI(bool* aResult) override;
NS_IMETHOD IsL10nAllowed(nsIURI* aURI, bool* aResult) override;
NS_IMETHOD GetAboutModuleFlags(uint32_t* flags) override;
NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;

View File

@ -394,6 +394,14 @@ interface nsIPrincipal : nsISupports
[infallible]
boolean isURIInList(in ACString list);
/**
* Check if the Principal's URI is a content-accessible about: page
*
* May be called from any thread.
*/
[infallible]
boolean isContentAccessibleAboutURI();
/**
* Uses NS_Security Compare to determine if the
* other URI is same-origin as the uri of the Principal

View File

@ -16237,17 +16237,26 @@ void Document::SendPageUseCounters() {
bool Document::RecomputeResistFingerprinting() {
const bool previous = mShouldResistFingerprinting;
if (mParentDocument &&
(NodePrincipal()->Equals(mParentDocument->NodePrincipal()) ||
NodePrincipal()->GetIsNullPrincipal())) {
// If we have a parent document, defer to it only when we have a null
// principal (e.g. a sandboxed iframe or a data: uri) or when the parent
// document's principal matches. This means we will defer about:blank,
// about:srcdoc, blob and same-origin iframes to the parent, but not
// cross-origin iframes.
mShouldResistFingerprinting = !nsContentUtils::IsChromeDoc(this) &&
mParentDocument->ShouldResistFingerprinting(
RFPTarget::IsAlwaysEnabledForPrecompute);
RefPtr<BrowsingContext> opener =
GetBrowsingContext() ? GetBrowsingContext()->GetOpener() : nullptr;
// If we have a parent or opener document, defer to it only when we have a
// null principal (e.g. a sandboxed iframe or a data: uri) or when the
// document's principal matches. This means we will defer about:blank,
// about:srcdoc, blob and same-origin iframes/popups to the parent/opener,
// but not cross-origin ones. Cross-origin iframes/popups may inherit a
// CookieJarSettings.mShouldRFP = false bit however, which will be respected.
auto shouldInheritFrom = [this](Document* aDoc) {
return aDoc && (this->NodePrincipal()->Equals(aDoc->NodePrincipal()) ||
this->NodePrincipal()->GetIsNullPrincipal());
};
if (shouldInheritFrom(mParentDocument)) {
mShouldResistFingerprinting = mParentDocument->ShouldResistFingerprinting(
RFPTarget::IsAlwaysEnabledForPrecompute);
} else if (opener && shouldInheritFrom(opener->GetDocument())) {
mShouldResistFingerprinting =
opener->GetDocument()->ShouldResistFingerprinting(
RFPTarget::IsAlwaysEnabledForPrecompute);
} else {
mShouldResistFingerprinting =
!nsContentUtils::IsChromeDoc(this) &&

View File

@ -2164,6 +2164,7 @@ bool nsContentUtils::ShouldResistFingerprinting(nsIGlobalObject* aGlobalObject,
}
// Newer Should RFP Functions ----------------------------------
// Utilities ---------------------------------------------------
inline void LogDomainAndPrefList(const char* exemptedDomainsPrefName,
nsAutoCString& url, bool isExemptDomain) {
@ -2198,9 +2199,29 @@ inline bool CookieJarSettingsSaysShouldResistFingerprinting(
return cookieJarSettings->GetShouldResistFingerprinting();
}
inline bool SchemeSaysShouldNotResistFingerprinting(nsIURI* aURI) {
return aURI->SchemeIs("chrome") || aURI->SchemeIs("resource") ||
aURI->SchemeIs("view-source") || aURI->SchemeIs("moz-extension") ||
(aURI->SchemeIs("about") && !NS_IsContentAccessibleAboutURI(aURI));
}
inline bool SchemeSaysShouldNotResistFingerprinting(nsIPrincipal* aPrincipal) {
if (aPrincipal->SchemeIs("chrome") || aPrincipal->SchemeIs("resource") ||
aPrincipal->SchemeIs("view-source") ||
aPrincipal->SchemeIs("moz-extension")) {
return true;
}
bool isSpecialAboutURI;
Unused << aPrincipal->IsContentAccessibleAboutURI(&isSpecialAboutURI);
return isSpecialAboutURI;
}
const char* kExemptedDomainsPrefName =
"privacy.resistFingerprinting.exemptedDomains";
// Functions ---------------------------------------------------
/* static */
bool nsContentUtils::ShouldResistFingerprinting(const char* aJustification,
RFPTarget aTarget) {
@ -2338,9 +2359,7 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous(
}
// Exclude internal schemes and web extensions
if (aURI->SchemeIs("about") || aURI->SchemeIs("chrome") ||
aURI->SchemeIs("resource") || aURI->SchemeIs("view-source") ||
aURI->SchemeIs("moz-extension")) {
if (SchemeSaysShouldNotResistFingerprinting(aURI)) {
return false;
}
@ -2422,9 +2441,8 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous(
}
}
// Exclude internal schemes
if (aPrincipal->SchemeIs("about") || aPrincipal->SchemeIs("chrome") ||
aPrincipal->SchemeIs("resource") || aPrincipal->SchemeIs("view-source")) {
// Exclude internal schemes and web extensions
if (SchemeSaysShouldNotResistFingerprinting(aPrincipal)) {
return false;
}
@ -2452,18 +2470,18 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous(
// So perform this last-ditch check for that scenario.
// We arbitrarily use https as the scheme, but it doesn't matter.
nsCOMPtr<nsIURI> uri;
nsresult rv;
if (isExemptDomain && StaticPrefs::privacy_firstparty_isolate() &&
!originAttributes.mFirstPartyDomain.IsEmpty()) {
rv = NS_NewURI(getter_AddRefs(uri),
u"https://"_ns + originAttributes.mFirstPartyDomain);
nsresult rv =
NS_NewURI(getter_AddRefs(uri),
u"https://"_ns + originAttributes.mFirstPartyDomain);
if (!NS_FAILED(rv)) {
isExemptDomain =
nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
}
} else if (isExemptDomain && !originAttributes.mPartitionKey.IsEmpty()) {
rv = NS_NewURI(getter_AddRefs(uri),
u"https://"_ns + originAttributes.mPartitionKey);
nsresult rv = NS_NewURI(getter_AddRefs(uri),
u"https://"_ns + originAttributes.mPartitionKey);
if (!NS_FAILED(rv)) {
isExemptDomain =
nsContentUtils::IsURIInPrefList(uri, kExemptedDomainsPrefName);
@ -2473,6 +2491,8 @@ bool nsContentUtils::ShouldResistFingerprinting_dangerous(
return !isExemptDomain;
}
// --------------------------------------------------------------------
/* static */
void nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
int32_t aChromeWidth, int32_t aChromeHeight, int32_t aScreenWidth,

View File

@ -421,15 +421,27 @@ LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
}
#endif
// Let's take the current cookie behavior and current cookie permission
// for the documents' loadInfo. Note that for any other loadInfos,
// cookieBehavior will be BEHAVIOR_REJECT for security reasons.
bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
// If we think we should not resist fingerprinting, defer to the opener's
// RFP bit (if there is an opener.) If the opener is also exempted, it stays
// true, otherwise we will put a false into the CJS and that will be respected
// on this document.
bool shouldResistFingerprinting =
nsContentUtils::ShouldResistFingerprinting_dangerous(
aURI, mOriginAttributes,
"We are creating CookieJarSettings, so we can't have one already.",
RFPTarget::IsAlwaysEnabledForPrecompute);
RefPtr<BrowsingContext> opener = aBrowsingContext->GetOpener();
if (!shouldResistFingerprinting && opener &&
opener->GetCurrentWindowContext()) {
shouldResistFingerprinting =
opener->GetCurrentWindowContext()->ShouldResistFingerprinting();
}
const bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0;
// Let's take the current cookie behavior and current cookie permission
// for the documents' loadInfo. Note that for any other loadInfos,
// cookieBehavior will be BEHAVIOR_REJECT for security reasons.
mCookieJarSettings = CookieJarSettings::Create(
isPrivate ? CookieJarSettings::ePrivate : CookieJarSettings::eRegular,
shouldResistFingerprinting);

View File

@ -33,13 +33,13 @@ As you can see in the callgraph below, directly calling a *dangerous* function w
SRFP_channel["ShouldResistFingerprinting(nsIChannel*)"]
click SRFP_channel href "https://searchfox.org/mozilla-central/search?q=symbol:_ZN14nsContentUtils26ShouldResistFingerprintingEP10nsIChannelN7mozilla9RFPTargetE&redirect=false"
SRFP_uri["ShouldResistFingerprinting_dangerous(nsIURI*, OriginAttributes)<br />PBM Check<br />Scheme (inc WebExtension) Check<br />URI Exempt Check"]
SRFP_uri["ShouldResistFingerprinting_dangerous(nsIURI*, OriginAttributes)<br />PBM Check<br />Scheme (inc WebExtension) Check<br />About Page Check<br />URI Exempt Check"]
click SRFP_uri href "https://searchfox.org/mozilla-central/search?q=symbol:_ZN14nsContentUtils36ShouldResistFingerprinting_dangerousEP6nsIURIRKN7mozilla16OriginAttributesEPKcNS2_9RFPTargetE&redirect=false"
SRFP_loadinfo["ShouldResistFingerprinting(nsILoadInfo)<br />CookieJarSettingsSaysShouldResistFingerprinting Check<br />System Principal Check"]
click SRFP_loadinfo href "https://searchfox.org/mozilla-central/search?q=symbol:_ZN14nsContentUtils26ShouldResistFingerprintingEP11nsILoadInfoN7mozilla9RFPTargetE&redirect=false"
SRFP_principal["ShouldResistFingerprinting_dangerous(nsIPrincipal*)<br />System Principal Check<br />PBM Check<br />Scheme Check<br />Web Extension Principal Check<br />URI Exempt Check"]
SRFP_principal["ShouldResistFingerprinting_dangerous(nsIPrincipal*)<br />System Principal Check<br />PBM Check<br />Scheme Check<br />About Page Check<br />Web Extension Principal Check<br />URI Exempt Check"]
click SRFP_principal href "https://searchfox.org/mozilla-central/search?q=symbol:_ZN14nsContentUtils36ShouldResistFingerprinting_dangerousEP12nsIPrincipalPKcN7mozilla9RFPTargetE&redirect=false"