diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/cookie/CookieServiceChild.cpp | 634 |
1 files changed, 634 insertions, 0 deletions
diff --git a/netwerk/cookie/CookieServiceChild.cpp b/netwerk/cookie/CookieServiceChild.cpp new file mode 100644 index 0000000000..a20d1d2b12 --- /dev/null +++ b/netwerk/cookie/CookieServiceChild.cpp @@ -0,0 +1,634 @@ +/* -*- 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 "nsIChannel.h" +#include "nsIClassifiedChannel.h" +#include "nsIHttpChannel.h" +#include "nsIEffectiveTLDService.h" +#include "nsIURI.h" +#include "nsIPrefBranch.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "ThirdPartyUtil.h" + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +// Pref string constants +static const char kCookieMoveIntervalSecs[] = + "network.cookie.move.interval_sec"; + +static StaticRefPtr<CookieServiceChild> gCookieChildService; +static uint32_t gMoveCookiesIntervalSeconds = 10; + +already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() { + if (!gCookieChildService) { + gCookieChildService = new CookieServiceChild(); + ClearOnShutdown(&gCookieChildService); + } + + return do_AddRef(gCookieChildService); +} + +NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService, nsIObserver, + nsITimerCallback, 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"); + + // Init our prefs and observer. + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + NS_WARNING_ASSERTION(prefBranch, "no prefservice"); + if (prefBranch) { + prefBranch->AddObserver(kCookieMoveIntervalSecs, this, true); + PrefChanged(prefBranch); + } + + nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + } +} + +void CookieServiceChild::MoveCookies() { + TimeStamp start = TimeStamp::Now(); + for (auto iter = mCookiesMap.Iter(); !iter.Done(); iter.Next()) { + CookiesList* cookiesList = iter.UserData(); + CookiesList newCookiesList; + for (uint32_t i = 0; i < cookiesList->Length(); ++i) { + Cookie* cookie = cookiesList->ElementAt(i); + RefPtr<Cookie> newCookie = cookie->Clone(); + newCookiesList.AppendElement(newCookie); + } + *cookiesList = std::move(newCookiesList); + } + + Telemetry::AccumulateTimeDelta(Telemetry::COOKIE_TIME_MOVING_MS, start); +} + +NS_IMETHODIMP +CookieServiceChild::Notify(nsITimer* aTimer) { + if (aTimer == mCookieTimer) { + MoveCookies(); + } else { + MOZ_CRASH("Unknown timer"); + } + return NS_OK; +} + +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 isSameSiteForeign = CookieCommons::IsSameSiteForeign(aChannel, uri); + SendPrepareCookieList( + uri, result.contains(ThirdPartyAnalysis::IsForeign), + result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), + result.contains(ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), + result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted), + rejectedReason, isSafeTopLevelNav, isSameSiteForeign, 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); + 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(); +} + +void CookieServiceChild::PrefChanged(nsIPrefBranch* aPrefBranch) { + int32_t val; + if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookieMoveIntervalSecs, &val))) { + gMoveCookiesIntervalSeconds = clamped<uint32_t>(val, 0, 3600); + if (gMoveCookiesIntervalSeconds && !mCookieTimer) { + NS_NewTimerWithCallback(getter_AddRefs(mCookieTimer), this, + gMoveCookiesIntervalSeconds * 1000, + nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY); + } + if (!gMoveCookiesIntervalSeconds && mCookieTimer) { + mCookieTimer->Cancel(); + mCookieTimer = nullptr; + } + if (mCookieTimer) { + mCookieTimer->SetDelay(gMoveCookiesIntervalSeconds * 1000); + } + } +} + +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.LookupOrAdd(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->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::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* /*aData*/) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + if (mCookieTimer) { + mCookieTimer->Cancel(); + mCookieTimer = nullptr; + } + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + } + } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject); + if (prefBranch) { + PrefChanged(prefBranch); + } + } else { + MOZ_ASSERT(false, "unexpected topic!"); + } + + return NS_OK; +} + +NS_IMETHODIMP +CookieServiceChild::GetCookieStringFromDocument(Document* aDocument, + nsACString& aCookieString) { + NS_ENSURE_ARG(aDocument); + + aCookieString.Truncate(); + + nsCOMPtr<nsIPrincipal> principal = aDocument->EffectiveStoragePrincipal(); + + if (!CookieCommons::IsSchemeSupported(principal)) { + return NS_OK; + } + + nsICookie::schemeType schemeType = + CookieCommons::PrincipalToSchemeType(principal); + + 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; + principal->GetAsciiHost(hostFromURI); + + 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) { + thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow, + nullptr, nullptr); + } + + 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; + } + + // if the cookie is secure and the host scheme isn't, we can't send it + if (cookie->IsSecure() && !isPotentiallyTrustworthy) { + continue; + } + + if (!CookieCommons::MaybeCompareScheme(cookie, schemeType)) { + 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( + 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) { + thirdParty = nsContentUtils::IsThirdPartyWindowOrChannel(innerWindow, + nullptr, nullptr); + } + + 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 + // 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. + 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()) && + existingCookie->IsHttpOnly()) { + // Can't overwrite an httponly cookie from a script context. + 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"); + 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 |