summaryrefslogtreecommitdiffstats
path: root/netwerk/cookie/CookieServiceChild.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/cookie/CookieServiceChild.cpp')
-rw-r--r--netwerk/cookie/CookieServiceChild.cpp577
1 files changed, 577 insertions, 0 deletions
diff --git a/netwerk/cookie/CookieServiceChild.cpp b/netwerk/cookie/CookieServiceChild.cpp
new file mode 100644
index 0000000000..ff796151e2
--- /dev/null
+++ b/netwerk/cookie/CookieServiceChild.cpp
@@ -0,0 +1,577 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "Cookie.h"
+#include "CookieCommons.h"
+#include "CookieLogging.h"
+#include "CookieService.h"
+#include "mozilla/net/CookieServiceChild.h"
+#include "mozilla/net/NeckoChannelParams.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "mozilla/net/NeckoChild.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StoragePrincipalHelper.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsICookieJarSettings.h"
+#include "nsIChannel.h"
+#include "nsIClassifiedChannel.h"
+#include "nsIHttpChannel.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsIURI.h"
+#include "nsIPrefBranch.h"
+#include "nsIWebProgressListener.h"
+#include "nsServiceManagerUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimeStamp.h"
+#include "ThirdPartyUtil.h"
+#include "nsIConsoleReportCollector.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace net {
+
+static StaticRefPtr<CookieServiceChild> gCookieChildService;
+
+already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() {
+ if (!gCookieChildService) {
+ gCookieChildService = new CookieServiceChild();
+ ClearOnShutdown(&gCookieChildService);
+ }
+
+ return do_AddRef(gCookieChildService);
+}
+
+NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService,
+ nsISupportsWeakReference)
+
+CookieServiceChild::CookieServiceChild() {
+ NS_ASSERTION(IsNeckoChild(), "not a child process");
+
+ auto* cc = static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager());
+ if (cc->IsShuttingDown()) {
+ return;
+ }
+
+ // This corresponds to Release() in DeallocPCookieService.
+ NS_ADDREF_THIS();
+
+ NeckoChild::InitNeckoChild();
+
+ // Create a child PCookieService actor.
+ gNeckoChild->SendPCookieServiceConstructor(this);
+
+ mThirdPartyUtil = ThirdPartyUtil::GetInstance();
+ NS_ASSERTION(mThirdPartyUtil, "couldn't get ThirdPartyUtil service");
+
+ mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ NS_ASSERTION(mTLDService, "couldn't get TLDService");
+}
+
+CookieServiceChild::~CookieServiceChild() { gCookieChildService = nullptr; }
+
+void CookieServiceChild::TrackCookieLoad(nsIChannel* aChannel) {
+ if (!CanSend()) {
+ return;
+ }
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, true, nullptr, RequireThirdPartyCheck, &rejectedReason);
+
+ nsCOMPtr<nsIURI> uri;
+ aChannel->GetURI(getter_AddRefs(uri));
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel);
+ bool hadCrossSiteRedirects = false;
+ bool isSameSiteForeign =
+ CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects);
+ SendPrepareCookieList(
+ uri, result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ rejectedReason, isSafeTopLevelNav, isSameSiteForeign,
+ hadCrossSiteRedirects, attrs);
+}
+
+IPCResult CookieServiceChild::RecvRemoveAll() {
+ mCookiesMap.Clear();
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvRemoveCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ nsCString baseDomain;
+ CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie.host(), baseDomain);
+ CookieKey key(baseDomain, aAttrs);
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ return IPC_OK();
+ }
+
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ if (cookie->Name().Equals(aCookie.name()) &&
+ cookie->Host().Equals(aCookie.host()) &&
+ cookie->Path().Equals(aCookie.path())) {
+ cookiesList->RemoveElementAt(i);
+ break;
+ }
+ }
+
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvAddCookie(const CookieStruct& aCookie,
+ const OriginAttributes& aAttrs) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookie, aAttrs);
+ RecordDocumentCookie(cookie, aAttrs);
+
+ // signal test code to check their cookie list
+ nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
+ if (obsService) {
+ obsService->NotifyObservers(nullptr, "cookie-content-filter-test", nullptr);
+ }
+
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvRemoveBatchDeletedCookies(
+ nsTArray<CookieStruct>&& aCookiesList,
+ nsTArray<OriginAttributes>&& aAttrsList) {
+ MOZ_ASSERT(aCookiesList.Length() == aAttrsList.Length());
+ for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
+ CookieStruct cookieStruct = aCookiesList.ElementAt(i);
+ RecvRemoveCookie(cookieStruct, aAttrsList.ElementAt(i));
+ }
+ return IPC_OK();
+}
+
+IPCResult CookieServiceChild::RecvTrackCookiesLoad(
+ nsTArray<CookieStruct>&& aCookiesList, const OriginAttributes& aAttrs) {
+ for (uint32_t i = 0; i < aCookiesList.Length(); i++) {
+ RefPtr<Cookie> cookie = Cookie::Create(aCookiesList[i], aAttrs);
+ cookie->SetIsHttpOnly(false);
+ RecordDocumentCookie(cookie, aAttrs);
+ }
+
+ return IPC_OK();
+}
+
+uint32_t CookieServiceChild::CountCookiesFromHashTable(
+ const nsACString& aBaseDomain, const OriginAttributes& aOriginAttrs) {
+ CookiesList* cookiesList = nullptr;
+
+ nsCString baseDomain;
+ CookieKey key(aBaseDomain, aOriginAttrs);
+ mCookiesMap.Get(key, &cookiesList);
+
+ return cookiesList ? cookiesList->Length() : 0;
+}
+
+/* static */ bool CookieServiceChild::RequireThirdPartyCheck(
+ nsILoadInfo* aLoadInfo) {
+ if (!aLoadInfo) {
+ return false;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ nsresult rv =
+ aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return false;
+ }
+
+ uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior();
+ return cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN ||
+ cookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN ||
+ cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER ||
+ cookieBehavior ==
+ nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN ||
+ StaticPrefs::network_cookie_thirdparty_sessionOnly() ||
+ StaticPrefs::network_cookie_thirdparty_nonsecureSessionOnly();
+}
+
+void CookieServiceChild::RecordDocumentCookie(Cookie* aCookie,
+ const OriginAttributes& aAttrs) {
+ nsAutoCString baseDomain;
+ CookieCommons::GetBaseDomainFromHost(mTLDService, aCookie->Host(),
+ baseDomain);
+
+ CookieKey key(baseDomain, aAttrs);
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ cookiesList = mCookiesMap.GetOrInsertNew(key);
+ }
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ if (cookie->Name().Equals(aCookie->Name()) &&
+ cookie->Host().Equals(aCookie->Host()) &&
+ cookie->Path().Equals(aCookie->Path())) {
+ if (cookie->Value().Equals(aCookie->Value()) &&
+ cookie->Expiry() == aCookie->Expiry() &&
+ cookie->IsSecure() == aCookie->IsSecure() &&
+ cookie->SameSite() == aCookie->SameSite() &&
+ cookie->RawSameSite() == aCookie->RawSameSite() &&
+ cookie->IsSession() == aCookie->IsSession() &&
+ cookie->IsHttpOnly() == aCookie->IsHttpOnly()) {
+ cookie->SetLastAccessed(aCookie->LastAccessed());
+ return;
+ }
+ cookiesList->RemoveElementAt(i);
+ break;
+ }
+ }
+
+ int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
+ if (aCookie->Expiry() <= currentTime) {
+ return;
+ }
+
+ cookiesList->AppendElement(aCookie);
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromDocument(dom::Document* aDocument,
+ nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ aCookieString.Truncate();
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
+
+ if (!CookieCommons::IsSchemeSupported(principal)) {
+ return NS_OK;
+ }
+
+ nsAutoCString baseDomain;
+ nsresult rv = CookieCommons::GetBaseDomain(principal, baseDomain);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, principal->OriginAttributesRef());
+ CookiesList* cookiesList = nullptr;
+ mCookiesMap.Get(key, &cookiesList);
+
+ if (!cookiesList) {
+ return NS_OK;
+ }
+
+ nsAutoCString hostFromURI;
+ rv = nsContentUtils::GetHostOrIPv6WithBrackets(principal, hostFromURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_OK;
+ }
+
+ nsAutoCString pathFromURI;
+ principal->GetFilePath(pathFromURI);
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ bool isPotentiallyTrustworthy =
+ principal->GetIsOriginPotentiallyTrustworthy();
+ int64_t currentTimeInUsec = PR_Now();
+ int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC;
+
+ cookiesList->Sort(CompareCookiesForSending());
+ for (uint32_t i = 0; i < cookiesList->Length(); i++) {
+ Cookie* cookie = cookiesList->ElementAt(i);
+ // check the host, since the base domain lookup is conservative.
+ if (!CookieCommons::DomainMatches(cookie, hostFromURI)) {
+ continue;
+ }
+
+ // We don't show HttpOnly cookies in content processes.
+ if (cookie->IsHttpOnly()) {
+ continue;
+ }
+
+ if (thirdParty &&
+ !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
+ continue;
+ }
+
+ // do not display the cookie if it is secure and the host scheme isn't
+ if (cookie->IsSecure() && !isPotentiallyTrustworthy) {
+ continue;
+ }
+
+ // if the nsIURI path doesn't match the cookie path, don't send it back
+ if (!CookieCommons::PathMatches(cookie, pathFromURI)) {
+ continue;
+ }
+
+ // check if the cookie has expired
+ if (cookie->Expiry() <= currentTime) {
+ continue;
+ }
+
+ if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) {
+ if (!aCookieString.IsEmpty()) {
+ aCookieString.AppendLiteral("; ");
+ }
+ if (!cookie->Name().IsEmpty()) {
+ aCookieString.Append(cookie->Name().get());
+ aCookieString.AppendLiteral("=");
+ aCookieString.Append(cookie->Value().get());
+ } else {
+ aCookieString.Append(cookie->Value().get());
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::GetCookieStringFromHttp(nsIURI* /*aHostURI*/,
+ nsIChannel* /*aChannel*/,
+ nsACString& /*aCookieString*/) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieStringFromDocument(
+ dom::Document* aDocument, const nsACString& aCookieString) {
+ NS_ENSURE_ARG(aDocument);
+
+ nsCOMPtr<nsIURI> documentURI;
+ nsAutoCString baseDomain;
+ OriginAttributes attrs;
+
+ // This function is executed in this context, I don't need to keep objects
+ // alive.
+ auto hasExistingCookiesLambda = [&](const nsACString& aBaseDomain,
+ const OriginAttributes& aAttrs) {
+ return !!CountCookiesFromHashTable(aBaseDomain, aAttrs);
+ };
+
+ RefPtr<Cookie> cookie = CookieCommons::CreateCookieFromDocument(
+ aDocument, aCookieString, PR_Now(), mTLDService, mThirdPartyUtil,
+ hasExistingCookiesLambda, getter_AddRefs(documentURI), baseDomain, attrs);
+ if (!cookie) {
+ return NS_OK;
+ }
+
+ bool thirdParty = true;
+ nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow();
+ // in gtests we don't have a window, let's consider those requests as 3rd
+ // party.
+ if (innerWindow) {
+ ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance();
+
+ if (thirdPartyUtil) {
+ Unused << thirdPartyUtil->IsThirdPartyWindow(
+ innerWindow->GetOuterWindow(), nullptr, &thirdParty);
+ }
+ }
+
+ if (thirdParty &&
+ !CookieCommons::ShouldIncludeCrossSiteCookieForDocument(cookie)) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, attrs);
+ CookiesList* cookies = mCookiesMap.Get(key);
+
+ if (cookies) {
+ // We need to see if the cookie we're setting would overwrite an httponly
+ // or a secure one. This would not affect anything we send over the net
+ // (those come from the parent, which already checks this),
+ // but script could see an inconsistent view of things.
+
+ nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveCookiePrincipal();
+ bool isPotentiallyTrustworthy =
+ principal->GetIsOriginPotentiallyTrustworthy();
+
+ for (uint32_t i = 0; i < cookies->Length(); ++i) {
+ RefPtr<Cookie> existingCookie = cookies->ElementAt(i);
+ if (existingCookie->Name().Equals(cookie->Name()) &&
+ existingCookie->Host().Equals(cookie->Host()) &&
+ existingCookie->Path().Equals(cookie->Path())) {
+ // Can't overwrite an httponly cookie from a script context.
+ if (existingCookie->IsHttpOnly()) {
+ return NS_OK;
+ }
+
+ // prevent insecure cookie from overwriting a secure one in insecure
+ // context.
+ if (existingCookie->IsSecure() && !isPotentiallyTrustworthy) {
+ return NS_OK;
+ }
+ }
+ }
+ }
+
+ RecordDocumentCookie(cookie, attrs);
+
+ if (CanSend()) {
+ nsTArray<CookieStruct> cookiesToSend;
+ cookiesToSend.AppendElement(cookie->ToIPC());
+
+ // Asynchronously call the parent.
+ SendSetCookies(baseDomain, attrs, documentURI, false, cookiesToSend);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::SetCookieStringFromHttp(nsIURI* aHostURI,
+ const nsACString& aCookieString,
+ nsIChannel* aChannel) {
+ NS_ENSURE_ARG(aHostURI);
+ NS_ENSURE_ARG(aChannel);
+
+ if (!CookieCommons::IsSchemeSupported(aHostURI)) {
+ return NS_OK;
+ }
+
+ // Fast past: don't bother sending IPC messages about nullprincipal'd
+ // documents.
+ nsAutoCString scheme;
+ aHostURI->GetScheme(scheme);
+ if (scheme.EqualsLiteral("moz-nullprincipal")) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
+
+ uint32_t rejectedReason = 0;
+ ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel(
+ aChannel, false, aHostURI, RequireThirdPartyCheck, &rejectedReason);
+
+ nsCString cookieString(aCookieString);
+
+ OriginAttributes attrs = loadInfo->GetOriginAttributes();
+ StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes(
+ aChannel, attrs);
+
+ bool requireHostMatch;
+ nsCString baseDomain;
+ CookieCommons::GetBaseDomain(mTLDService, aHostURI, baseDomain,
+ requireHostMatch);
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ CookieCommons::GetCookieJarSettings(aChannel);
+
+ nsCOMPtr<nsIConsoleReportCollector> crc = do_QueryInterface(aChannel);
+
+ CookieStatus cookieStatus = CookieService::CheckPrefs(
+ crc, cookieJarSettings, aHostURI,
+ result.contains(ThirdPartyAnalysis::IsForeign),
+ result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource),
+ result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted),
+ aCookieString, CountCookiesFromHashTable(baseDomain, attrs), attrs,
+ &rejectedReason);
+
+ if (cookieStatus != STATUS_ACCEPTED &&
+ cookieStatus != STATUS_ACCEPT_SESSION) {
+ return NS_OK;
+ }
+
+ CookieKey key(baseDomain, attrs);
+
+ nsTArray<CookieStruct> cookiesToSend;
+
+ int64_t currentTimeInUsec = PR_Now();
+
+ bool addonAllowsLoad = false;
+ nsCOMPtr<nsIURI> finalChannelURI;
+ NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalChannelURI));
+ addonAllowsLoad = BasePrincipal::Cast(loadInfo->TriggeringPrincipal())
+ ->AddonAllowsLoad(finalChannelURI);
+
+ bool isForeignAndNotAddon = false;
+ if (!addonAllowsLoad) {
+ mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI,
+ &isForeignAndNotAddon);
+ }
+
+ bool moreCookies;
+ do {
+ CookieStruct cookieData;
+ bool canSetCookie = false;
+ moreCookies = CookieService::CanSetCookie(
+ aHostURI, baseDomain, cookieData, requireHostMatch, cookieStatus,
+ cookieString, true, isForeignAndNotAddon, crc, canSetCookie);
+ if (!canSetCookie) {
+ continue;
+ }
+
+ // check permissions from site permission list.
+ if (!CookieCommons::CheckCookiePermission(aChannel, cookieData)) {
+ COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieString,
+ "cookie rejected by permission manager");
+ constexpr auto CONSOLE_REJECTION_CATEGORY = "cookiesRejection"_ns;
+ CookieLogging::LogMessageToConsole(
+ crc, aHostURI, nsIScriptError::warningFlag,
+ CONSOLE_REJECTION_CATEGORY, "CookieRejectedByPermissionManager"_ns,
+ AutoTArray<nsString, 1>{
+ NS_ConvertUTF8toUTF16(cookieData.name()),
+ });
+ CookieCommons::NotifyRejected(
+ aHostURI, aChannel,
+ nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION,
+ OPERATION_WRITE);
+ continue;
+ }
+
+ RefPtr<Cookie> cookie = Cookie::Create(cookieData, attrs);
+ MOZ_ASSERT(cookie);
+
+ cookie->SetLastAccessed(currentTimeInUsec);
+ cookie->SetCreationTime(
+ Cookie::GenerateUniqueCreationTime(currentTimeInUsec));
+
+ RecordDocumentCookie(cookie, attrs);
+ cookiesToSend.AppendElement(cookieData);
+ } while (moreCookies);
+
+ // Asynchronously call the parent.
+ if (CanSend() && !cookiesToSend.IsEmpty()) {
+ SendSetCookies(baseDomain, attrs, aHostURI, true, cookiesToSend);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CookieServiceChild::RunInTransaction(
+ nsICookieTransactionCallback* /*aCallback*/) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+} // namespace net
+} // namespace mozilla