summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp')
-rw-r--r--toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp434
1 files changed, 434 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp b/toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp
new file mode 100644
index 0000000000..747427abcc
--- /dev/null
+++ b/toolkit/components/antitracking/AntiTrackingRedirectHeuristic.cpp
@@ -0,0 +1,434 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AntiTrackingLog.h"
+#include "AntiTrackingRedirectHeuristic.h"
+#include "ContentBlockingAllowList.h"
+#include "ContentBlockingUserInteraction.h"
+#include "StorageAccessAPIHelper.h"
+
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/net/CookieJarSettings.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/Telemetry.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsICookieService.h"
+#include "nsIHttpChannel.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsIScriptError.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsScriptSecurityManager.h"
+
+namespace mozilla {
+
+namespace {
+
+// The helper function to check if we need to check the redirect heuristic for
+// ETP later when we know the classification flags of the new channel. This
+// check is from the perspective of the old channel. We don't check for the new
+// channel because the classification flags are not ready yet when we call this
+// function.
+bool ShouldCheckRedirectHeuristicETP(nsIChannel* aOldChannel, nsIURI* aOldURI,
+ nsIPrincipal* aOldPrincipal) {
+ nsCOMPtr<nsIClassifiedChannel> oldClassifiedChannel =
+ do_QueryInterface(aOldChannel);
+
+ if (!oldClassifiedChannel) {
+ LOG_SPEC(("Ignoring the redirect from %s because there is no "
+ "nsIClassifiedChannel interface",
+ _spec),
+ aOldURI);
+ return false;
+ }
+
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
+ MOZ_ASSERT(oldLoadInfo);
+
+ bool allowedByPreviousRedirect =
+ oldLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain();
+
+ uint32_t oldClassificationFlags =
+ oldClassifiedChannel->GetFirstPartyClassificationFlags();
+
+ // We will skip this check if we have granted storage access before so that we
+ // can grant the storage access to the rest of the chain.
+ if (!net::UrlClassifierCommon::IsTrackingClassificationFlag(
+ oldClassificationFlags, NS_UsePrivateBrowsing(aOldChannel)) &&
+ !allowedByPreviousRedirect) {
+ // This is not a tracking -> non-tracking redirect.
+ LOG_SPEC(("Ignoring the redirect from %s because it's not tracking to "
+ "non-tracking.",
+ _spec),
+ aOldURI);
+ return false;
+ }
+
+ if (!ContentBlockingUserInteraction::Exists(aOldPrincipal)) {
+ LOG_SPEC(("Ignoring redirect for %s because no user-interaction on "
+ "tracker",
+ _spec),
+ aOldURI);
+ return false;
+ }
+
+ return true;
+}
+
+// The helper function to decide and set the storage access after we know the
+// classification flags of the new channel.
+bool ShouldRedirectHeuristicApplyETP(nsIChannel* aNewChannel, nsIURI* aNewURI) {
+ nsCOMPtr<nsIClassifiedChannel> newClassifiedChannel =
+ do_QueryInterface(aNewChannel);
+
+ if (!aNewChannel) {
+ LOG_SPEC(("Ignoring the redirect to %s because there is no "
+ "nsIClassifiedChannel interface",
+ _spec),
+ aNewURI);
+ return false;
+ }
+
+ // We're looking at the first-party classification flags because we're
+ // interested in first-party redirects.
+ uint32_t newClassificationFlags =
+ newClassifiedChannel->GetFirstPartyClassificationFlags();
+
+ if (net::UrlClassifierCommon::IsTrackingClassificationFlag(
+ newClassificationFlags, NS_UsePrivateBrowsing(aNewChannel))) {
+ // This is not a tracking -> non-tracking redirect.
+ LOG_SPEC(("Ignoring the redirect to %s because it's not tracking to "
+ "non-tracking.",
+ _spec),
+ aNewURI);
+ return false;
+ }
+
+ return true;
+}
+
+// The helper function to check if we need to check the redirect heuristic for
+// RejectForeign later from the old channel perspective.
+bool ShouldCheckRedirectHeuristicRejectForeign(nsIChannel* aOldChannel,
+ nsIURI* aOldURI,
+ nsIPrincipal* aOldPrincipal) {
+ if (!ContentBlockingUserInteraction::Exists(aOldPrincipal)) {
+ LOG_SPEC(("Ignoring redirect from %s because no user-interaction on "
+ "old origin",
+ _spec),
+ aOldURI);
+ return false;
+ }
+
+ return true;
+}
+
+bool ShouldRedirectHeuristicApply(nsIChannel* aNewChannel, nsIURI* aNewURI) {
+ nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
+ MOZ_ASSERT(newLoadInfo);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+
+ nsresult rv =
+ newLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the cookieJarSetting from the channel"));
+ return false;
+ }
+
+ uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ return ShouldRedirectHeuristicApplyETP(aNewChannel, aNewURI);
+ }
+
+ // We will grant the storage access regardless the new channel is a tracker or
+ // not for RejectForeign.
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
+ StaticPrefs::network_cookie_rejectForeignWithExceptions_enabled()) {
+ return true;
+ }
+
+ LOG((
+ "Heuristic doesn't apply because the cookieBehavior doesn't require it"));
+ return false;
+}
+
+bool ShouldCheckRedirectHeuristic(nsIChannel* aOldChannel, nsIURI* aOldURI,
+ nsIPrincipal* aOldPrincipal) {
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
+ MOZ_ASSERT(oldLoadInfo);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+
+ nsresult rv =
+ oldLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the cookieJarSettings from the old channel"));
+ return false;
+ }
+
+ uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ return ShouldCheckRedirectHeuristicETP(aOldChannel, aOldURI, aOldPrincipal);
+ }
+
+ if (cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN &&
+ StaticPrefs::network_cookie_rejectForeignWithExceptions_enabled()) {
+ return ShouldCheckRedirectHeuristicRejectForeign(aOldChannel, aOldURI,
+ aOldPrincipal);
+ }
+
+ LOG(
+ ("Heuristic doesn't be needed because the cookieBehavior doesn't require "
+ "it"));
+ return false;
+}
+
+} // namespace
+
+void PrepareForAntiTrackingRedirectHeuristic(nsIChannel* aOldChannel,
+ nsIURI* aOldURI,
+ nsIChannel* aNewChannel,
+ nsIURI* aNewURI) {
+ MOZ_ASSERT(aOldChannel);
+ MOZ_ASSERT(aOldURI);
+ MOZ_ASSERT(aNewChannel);
+ MOZ_ASSERT(aNewURI);
+
+ // This heuristic works only on the parent process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::privacy_antitracking_enableWebcompat() ||
+ !StaticPrefs::privacy_restrict3rdpartystorage_heuristic_redirect()) {
+ return;
+ }
+
+ nsCOMPtr<nsIHttpChannel> oldChannel = do_QueryInterface(aOldChannel);
+ if (!oldChannel) {
+ return;
+ }
+
+ nsCOMPtr<nsIHttpChannel> newChannel = do_QueryInterface(aNewChannel);
+ if (!newChannel) {
+ return;
+ }
+
+ LOG_SPEC2(("Preparing redirect-heuristic for the redirect %s -> %s", _spec1,
+ _spec2),
+ aOldURI, aNewURI);
+
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
+ MOZ_ASSERT(oldLoadInfo);
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
+ MOZ_ASSERT(newLoadInfo);
+
+ // We need to clear the flag first because the new loadInfo was cloned from
+ // the old loadInfo.
+ newLoadInfo->SetNeedForCheckingAntiTrackingHeuristic(false);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ oldLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't get the cookieJarSettings"));
+ return;
+ }
+
+ int32_t behavior = cookieJarSettings->GetCookieBehavior();
+
+ if (!cookieJarSettings->GetRejectThirdPartyContexts()) {
+ LOG(
+ ("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
+ "early",
+ behavior));
+ return;
+ }
+
+ MOZ_ASSERT(
+ behavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ behavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN ||
+ net::CookieJarSettings::IsRejectThirdPartyWithExceptions(behavior));
+
+ ExtContentPolicyType contentType =
+ oldLoadInfo->GetExternalContentPolicyType();
+ if (contentType != ExtContentPolicy::TYPE_DOCUMENT ||
+ !aOldChannel->IsDocument()) {
+ LOG_SPEC(("Ignoring redirect for %s because it's not a document", _spec),
+ aOldURI);
+ // We care about document redirects only.
+ return;
+ }
+
+ if (ContentBlockingAllowList::Check(newChannel)) {
+ return;
+ }
+
+ nsIScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+
+ const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& chain =
+ oldLoadInfo->RedirectChain();
+
+ if (oldLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain() &&
+ !chain.IsEmpty()) {
+ rv = chain[0]->GetPrincipal(getter_AddRefs(oldPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the principal from the redirect chain"));
+ return;
+ }
+ } else {
+ rv = ssm->GetChannelResultPrincipal(aOldChannel,
+ getter_AddRefs(oldPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the principal from the previous channel"));
+ return;
+ }
+ }
+
+ newLoadInfo->SetNeedForCheckingAntiTrackingHeuristic(
+ ShouldCheckRedirectHeuristic(aOldChannel, aOldURI, oldPrincipal));
+}
+
+void FinishAntiTrackingRedirectHeuristic(nsIChannel* aNewChannel,
+ nsIURI* aNewURI) {
+ MOZ_ASSERT(aNewChannel);
+ MOZ_ASSERT(aNewURI);
+
+ // This heuristic works only on the parent process.
+ if (!XRE_IsParentProcess()) {
+ return;
+ }
+
+ if (!StaticPrefs::privacy_antitracking_enableWebcompat() ||
+ !StaticPrefs::privacy_restrict3rdpartystorage_heuristic_redirect()) {
+ return;
+ }
+
+ nsCOMPtr<nsIHttpChannel> newChannel = do_QueryInterface(aNewChannel);
+ if (!newChannel) {
+ return;
+ }
+
+ LOG_SPEC(("Finishing redirect-heuristic for the redirect to %s", _spec),
+ aNewURI);
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+ MOZ_ASSERT(newLoadInfo);
+
+ // Bailing out early if there is no need to do the heuristic.
+ if (!newLoadInfo->GetNeedForCheckingAntiTrackingHeuristic()) {
+ return;
+ }
+
+ if (!ShouldRedirectHeuristicApply(aNewChannel, aNewURI)) {
+ return;
+ }
+
+ nsIScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& chain =
+ newLoadInfo->RedirectChain();
+
+ if (chain.IsEmpty()) {
+ LOG(("Can't obtain the redirect chain"));
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ uint32_t idx =
+ newLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain()
+ ? 0
+ : chain.Length() - 1;
+
+ nsresult rv = chain[idx]->GetPrincipal(getter_AddRefs(oldPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the principal from the redirect chain"));
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> newPrincipal;
+ rv =
+ ssm->GetChannelResultPrincipal(aNewChannel, getter_AddRefs(newPrincipal));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't obtain the principal from the new channel"));
+ return;
+ }
+
+ if (oldPrincipal->Equals(newPrincipal)) {
+ LOG(("No permission needed for same principals."));
+ return;
+ }
+
+ nsAutoCString oldOrigin;
+ rv = oldPrincipal->GetOrigin(oldOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't get the origin from the Principal"));
+ return;
+ }
+
+ nsAutoCString newOrigin;
+ rv = nsContentUtils::GetASCIIOrigin(aNewURI, newOrigin);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOG(("Can't get the origin from the URI"));
+ return;
+ }
+
+ LOG(("Adding a first-party storage exception for %s...", newOrigin.get()));
+
+ LOG(("Saving the permission: oldOrigin=%s, grantedOrigin=%s", oldOrigin.get(),
+ newOrigin.get()));
+
+ // Any new redirect from this loadInfo must be considered as granted.
+ newLoadInfo->SetAllowListFutureDocumentsCreatedFromThisRedirectChain(true);
+
+ uint64_t innerWindowID;
+ Unused << newChannel->GetTopLevelContentWindowId(&innerWindowID);
+
+ nsAutoString errorText;
+ AutoTArray<nsString, 2> params = {NS_ConvertUTF8toUTF16(newOrigin),
+ NS_ConvertUTF8toUTF16(oldOrigin)};
+ rv = nsContentUtils::FormatLocalizedString(
+ nsContentUtils::eNECKO_PROPERTIES, "CookieAllowedForOriginByHeuristic",
+ params, errorText);
+ if (NS_SUCCEEDED(rv)) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ errorText, nsIScriptError::warningFlag, ANTITRACKING_CONSOLE_CATEGORY,
+ innerWindowID);
+ }
+
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::StorageGranted);
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_STORAGE_ACCESS_GRANTED_COUNT::Redirect);
+
+ // We don't care about this promise because the operation is actually sync.
+ RefPtr<StorageAccessAPIHelper::ParentAccessGrantPromise> promise =
+ StorageAccessAPIHelper::SaveAccessForOriginOnParentProcess(
+ newPrincipal, oldPrincipal,
+ StorageAccessAPIHelper::StorageAccessPromptChoices::eAllow,
+ StaticPrefs::privacy_restrict3rdpartystorage_expiration_redirect());
+ Unused << promise;
+}
+
+} // namespace mozilla