summaryrefslogtreecommitdiffstats
path: root/toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp')
-rw-r--r--toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp343
1 files changed, 343 insertions, 0 deletions
diff --git a/toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp b/toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp
new file mode 100644
index 0000000000..c4b3ea3d2e
--- /dev/null
+++ b/toolkit/components/antitracking/DynamicFpiRedirectHeuristic.cpp
@@ -0,0 +1,343 @@
+/* -*- 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 "DynamicFpiRedirectHeuristic.h"
+#include "ContentBlockingAllowList.h"
+#include "ContentBlockingUserInteraction.h"
+#include "StorageAccessAPIHelper.h"
+
+#include "mozilla/net/HttpBaseChannel.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/Telemetry.h"
+#include "nsContentUtils.h"
+#include "nsIChannel.h"
+#include "nsICookieJarSettings.h"
+#include "nsICookieService.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsINavHistoryService.h"
+#include "nsIRedirectHistoryEntry.h"
+#include "nsIScriptError.h"
+#include "nsIURI.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsScriptSecurityManager.h"
+#include "nsToolkitCompsCID.h"
+
+namespace mozilla {
+
+namespace {
+
+nsresult GetBaseDomain(nsIURI* aURI, nsACString& aBaseDomain) {
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+
+ if (!tldService) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return tldService->GetBaseDomain(aURI, 0, aBaseDomain);
+}
+
+// check if there's any interacting visit within the given seconds
+bool HasEligibleVisit(
+ nsIURI* aURI,
+ int64_t aSinceInSec = StaticPrefs::
+ privacy_restrict3rdpartystorage_heuristic_recently_visited_time()) {
+ nsresult rv;
+
+ nsAutoCString baseDomain;
+ rv = GetBaseDomain(aURI, baseDomain);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsCOMPtr<nsINavHistoryService> histSrv =
+ do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+ if (!histSrv) {
+ return false;
+ }
+ nsCOMPtr<nsINavHistoryQuery> histQuery;
+ rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histQuery->SetDomain(baseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histQuery->SetDomainIsHost(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ PRTime beginTime = PR_Now() - PRTime(PR_USEC_PER_SEC) * aSinceInSec;
+ rv = histQuery->SetBeginTime(beginTime);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
+ rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv =
+ histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_VISIT);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histQueryOpts->SetMaxResults(1);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histQueryOpts->SetQueryType(
+ nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsCOMPtr<nsINavHistoryResult> histResult;
+ rv = histSrv->ExecuteQuery(histQuery, histQueryOpts,
+ getter_AddRefs(histResult));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
+ rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histResultContainer->SetContainerOpen(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ uint32_t childCount = 0;
+ rv = histResultContainer->GetChildCount(&childCount);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ rv = histResultContainer->SetContainerOpen(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ return childCount > 0;
+}
+
+void AddConsoleReport(nsIChannel* aNewChannel, nsIURI* aNewURI,
+ const nsACString& aOldOrigin,
+ const nsACString& aNewOrigin) {
+ nsCOMPtr<net::HttpBaseChannel> httpChannel = do_QueryInterface(aNewChannel);
+ if (!httpChannel) {
+ return;
+ }
+
+ nsAutoCString uri;
+ nsresult rv = aNewURI->GetSpec(uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ AutoTArray<nsString, 2> params = {NS_ConvertUTF8toUTF16(aNewOrigin),
+ NS_ConvertUTF8toUTF16(aOldOrigin)};
+
+ httpChannel->AddConsoleReport(nsIScriptError::warningFlag,
+ ANTITRACKING_CONSOLE_CATEGORY,
+ nsContentUtils::eNECKO_PROPERTIES, uri, 0, 0,
+ "CookieAllowedForFpiByHeuristic"_ns, params);
+}
+
+bool ShouldRedirectHeuristicApplyTrackingResource(nsIChannel* aOldChannel,
+ nsIURI* aOldURI,
+ nsIChannel* aNewChannel,
+ nsIURI* aNewURI) {
+ nsCOMPtr<nsIClassifiedChannel> classifiedOldChannel =
+ do_QueryInterface(aOldChannel);
+ if (!classifiedOldChannel) {
+ LOG_SPEC2(("Ignoring redirect for %s to %s because there is not "
+ "nsIClassifiedChannel interface",
+ _spec1, _spec2),
+ aOldURI, aNewURI);
+ return false;
+ }
+
+ // We're looking at the first-party classification flags because we're
+ // interested in first-party redirects.
+ uint32_t oldClassificationFlags =
+ classifiedOldChannel->GetFirstPartyClassificationFlags();
+
+ if (net::UrlClassifierCommon::IsTrackingClassificationFlag(
+ oldClassificationFlags, NS_UsePrivateBrowsing(aOldChannel))) {
+ // This is a redirect from tracking.
+ LOG_SPEC2(("Ignoring redirect for %s to %s because it's from tracking ",
+ _spec1, _spec2),
+ aOldURI, aNewURI);
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace
+
+void DynamicFpiRedirectHeuristic(nsIChannel* aOldChannel, nsIURI* aOldURI,
+ nsIChannel* aNewChannel, nsIURI* aNewURI) {
+ MOZ_ASSERT(aOldChannel);
+ MOZ_ASSERT(aOldURI);
+ MOZ_ASSERT(aNewChannel);
+ MOZ_ASSERT(aNewURI);
+
+ nsresult rv;
+
+ if (!StaticPrefs::privacy_antitracking_enableWebcompat() ||
+ !StaticPrefs::
+ privacy_restrict3rdpartystorage_heuristic_recently_visited()) {
+ return;
+ }
+
+ nsCOMPtr<nsIHttpChannel> oldChannel = do_QueryInterface(aOldChannel);
+ nsCOMPtr<nsIHttpChannel> newChannel = do_QueryInterface(aNewChannel);
+ if (!oldChannel || !newChannel) {
+ return;
+ }
+
+ LOG_SPEC(("Checking dfpi redirect-heuristic for %s", _spec), aOldURI);
+
+ nsCOMPtr<nsILoadInfo> oldLoadInfo = aOldChannel->LoadInfo();
+ MOZ_ASSERT(oldLoadInfo);
+
+ nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->LoadInfo();
+ MOZ_ASSERT(newLoadInfo);
+
+ 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;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ 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 (behavior !=
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) {
+ LOG(
+ ("Disabled by network.cookie.cookieBehavior pref (%d), bailing out "
+ "early",
+ behavior));
+ return;
+ }
+
+ nsIScriptSecurityManager* ssm =
+ nsScriptSecurityManager::GetScriptSecurityManager();
+ MOZ_ASSERT(ssm);
+
+ nsCOMPtr<nsIPrincipal> oldPrincipal;
+ const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& chain =
+ oldLoadInfo->RedirectChain();
+ if (!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 old channel"));
+ 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;
+ }
+
+ if (!ShouldRedirectHeuristicApplyTrackingResource(aOldChannel, aOldURI,
+ aNewChannel, aNewURI)) {
+ LOG_SPEC2(("Ignoring redirect for %s to %s because tracking test failed",
+ _spec1, _spec2),
+ aOldURI, aNewURI);
+ return;
+ }
+
+ if (!ContentBlockingUserInteraction::Exists(oldPrincipal) ||
+ !ContentBlockingUserInteraction::Exists(newPrincipal)) {
+ LOG_SPEC2(("Ignoring redirect for %s to %s because no user-interaction on "
+ "both pages",
+ _spec1, _spec2),
+ aOldURI, aNewURI);
+ 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;
+ }
+
+ if (!HasEligibleVisit(aOldURI) || !HasEligibleVisit(aNewURI)) {
+ LOG(("No previous visit record, bailing out early."));
+ return;
+ }
+
+ LOG(("Adding a first-party storage exception for %s...",
+ PromiseFlatCString(newOrigin).get()));
+
+ LOG(("Saving the permission: oldOrigin=%s, grantedOrigin=%s", oldOrigin.get(),
+ newOrigin.get()));
+
+ AddConsoleReport(aNewChannel, aNewURI, oldOrigin, newOrigin);
+
+ 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_visited());
+ Unused << promise;
+}
+
+} // namespace mozilla