CookieServiceChild.cpp (19941B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "Cookie.h" 7 #include "CookieCommons.h" 8 #include "CookieLogging.h" 9 #include "CookieNotification.h" 10 #include "CookieParser.h" 11 #include "CookieService.h" 12 #include "mozilla/net/CookieServiceChild.h" 13 #include "ErrorList.h" 14 #include "mozilla/net/HttpChannelChild.h" 15 #include "mozilla/net/NeckoChannelParams.h" 16 #include "mozilla/LoadInfo.h" 17 #include "mozilla/BasePrincipal.h" 18 #include "mozilla/ClearOnShutdown.h" 19 #include "mozilla/ConsoleReportCollector.h" 20 #include "mozilla/dom/ContentChild.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/glean/NetwerkMetrics.h" 23 #include "mozilla/ipc/URIUtils.h" 24 #include "mozilla/net/NeckoChild.h" 25 #include "mozilla/StaticPrefs_network.h" 26 #include "mozilla/StoragePrincipalHelper.h" 27 #include "nsNetCID.h" 28 #include "nsNetUtil.h" 29 #include "nsICookieJarSettings.h" 30 #include "nsIChannel.h" 31 #include "nsIClassifiedChannel.h" 32 #include "nsIHttpChannel.h" 33 #include "nsIEffectiveTLDService.h" 34 #include "nsIURI.h" 35 #include "nsIPrefBranch.h" 36 #include "nsIScriptSecurityManager.h" 37 #include "nsIWebProgressListener.h" 38 #include "nsQueryObject.h" 39 #include "nsServiceManagerUtils.h" 40 #include "mozilla/TimeStamp.h" 41 #include "ThirdPartyUtil.h" 42 #include "nsIConsoleReportCollector.h" 43 #include "mozilla/dom/WindowGlobalChild.h" 44 45 using namespace mozilla::ipc; 46 47 namespace mozilla { 48 namespace net { 49 50 static StaticRefPtr<CookieServiceChild> gCookieChildService; 51 52 already_AddRefed<CookieServiceChild> CookieServiceChild::GetSingleton() { 53 if (!gCookieChildService) { 54 gCookieChildService = new CookieServiceChild(); 55 gCookieChildService->Init(); 56 ClearOnShutdown(&gCookieChildService); 57 } 58 59 return do_AddRef(gCookieChildService); 60 } 61 62 NS_IMPL_ISUPPORTS(CookieServiceChild, nsICookieService, 63 nsISupportsWeakReference) 64 65 CookieServiceChild::CookieServiceChild() { NeckoChild::InitNeckoChild(); } 66 67 CookieServiceChild::~CookieServiceChild() { gCookieChildService = nullptr; } 68 69 void CookieServiceChild::Init() { 70 auto* cc = static_cast<mozilla::dom::ContentChild*>(gNeckoChild->Manager()); 71 if (cc->IsShuttingDown()) { 72 return; 73 } 74 75 // This corresponds to Release() in DeallocPCookieService. 76 NS_ADDREF_THIS(); 77 78 // Create a child PCookieService actor. Don't do this in the constructor 79 // since it could release 'this' on failure 80 gNeckoChild->SendPCookieServiceConstructor(this); 81 82 mThirdPartyUtil = ThirdPartyUtil::GetInstance(); 83 NS_ASSERTION(mThirdPartyUtil, "couldn't get ThirdPartyUtil service"); 84 85 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); 86 NS_ASSERTION(mTLDService, "couldn't get TLDService"); 87 } 88 89 RefPtr<GenericPromise> CookieServiceChild::TrackCookieLoad( 90 nsIChannel* aChannel) { 91 if (!CanSend()) { 92 return GenericPromise::CreateAndReject(NS_ERROR_NOT_AVAILABLE, __func__); 93 } 94 95 uint32_t rejectedReason = 0; 96 ThirdPartyAnalysisResult result = mThirdPartyUtil->AnalyzeChannel( 97 aChannel, true, nullptr, RequireThirdPartyCheck, &rejectedReason); 98 99 nsCOMPtr<nsIURI> uri; 100 aChannel->GetURI(getter_AddRefs(uri)); 101 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 102 103 OriginAttributes storageOriginAttributes = loadInfo->GetOriginAttributes(); 104 StoragePrincipalHelper::PrepareEffectiveStoragePrincipalOriginAttributes( 105 aChannel, storageOriginAttributes); 106 107 bool isSafeTopLevelNav = CookieCommons::IsSafeTopLevelNav(aChannel); 108 bool hadCrossSiteRedirects = false; 109 bool isSameSiteForeign = 110 CookieCommons::IsSameSiteForeign(aChannel, uri, &hadCrossSiteRedirects); 111 112 RefPtr<CookieServiceChild> self(this); 113 114 nsTArray<OriginAttributes> originAttributesList; 115 originAttributesList.AppendElement(storageOriginAttributes); 116 117 // CHIPS - If CHIPS is enabled the partitioned cookie jar is always available 118 // (and therefore the partitioned OriginAttributes), the unpartitioned cookie 119 // jar is only available in first-party or third-party with storageAccess 120 // contexts. 121 nsCOMPtr<nsICookieJarSettings> cookieJarSettings = 122 CookieCommons::GetCookieJarSettings(aChannel); 123 bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() && 124 !cookieJarSettings->GetBlockingAllContexts(); 125 bool isUnpartitioned = 126 !result.contains(ThirdPartyAnalysis::IsForeign) || 127 result.contains(ThirdPartyAnalysis::IsStorageAccessPermissionGranted); 128 if (isCHIPS && isUnpartitioned) { 129 // Assert that the storage originAttributes is empty. In other words, 130 // it's unpartitioned. 131 MOZ_ASSERT(storageOriginAttributes.mPartitionKey.IsEmpty()); 132 // Add the partitioned principal to principals. 133 OriginAttributes partitionedOriginAttributes; 134 StoragePrincipalHelper::GetOriginAttributes( 135 aChannel, partitionedOriginAttributes, 136 StoragePrincipalHelper::ePartitionedPrincipal); 137 originAttributesList.AppendElement(partitionedOriginAttributes); 138 // Only append the partitioned originAttributes if the partitionKey is set. 139 // The partitionKey could be empty for partitionKey in partitioned 140 // originAttributes if the channel is for privilege request, such as 141 // extension's requests. 142 if (!partitionedOriginAttributes.mPartitionKey.IsEmpty()) { 143 originAttributesList.AppendElement(partitionedOriginAttributes); 144 } 145 } 146 147 return SendGetCookieList( 148 uri, result.contains(ThirdPartyAnalysis::IsForeign), 149 result.contains(ThirdPartyAnalysis::IsThirdPartyTrackingResource), 150 result.contains( 151 ThirdPartyAnalysis::IsThirdPartySocialTrackingResource), 152 result.contains( 153 ThirdPartyAnalysis::IsStorageAccessPermissionGranted), 154 rejectedReason, isSafeTopLevelNav, isSameSiteForeign, 155 hadCrossSiteRedirects, originAttributesList) 156 ->Then( 157 GetCurrentSerialEventTarget(), __func__, 158 [self, uri](const nsTArray<CookieStructTable>& aCookiesListTable) { 159 for (auto& entry : aCookiesListTable) { 160 auto& cookies = entry.cookies(); 161 for (auto& cookieEntry : cookies) { 162 RefPtr<Cookie> cookie = 163 Cookie::Create(cookieEntry, entry.attrs()); 164 cookie->SetIsHttpOnly(false); 165 self->RecordDocumentCookie(cookie, entry.attrs()); 166 } 167 } 168 return GenericPromise::CreateAndResolve(true, __func__); 169 }, 170 [](const mozilla::ipc::ResponseRejectReason) { 171 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); 172 }); 173 } 174 175 IPCResult CookieServiceChild::RecvRemoveAll() { 176 mCookiesMap.Clear(); 177 178 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 179 if (obsService) { 180 obsService->NotifyObservers(nullptr, "content-removed-all-cookies", 181 nullptr); 182 } 183 return IPC_OK(); 184 } 185 186 IPCResult CookieServiceChild::RecvRemoveCookie( 187 const CookieStruct& aCookie, const OriginAttributes& aAttrs, 188 const Maybe<nsID>& aOperationID) { 189 RemoveSingleCookie(aCookie, aAttrs, aOperationID); 190 191 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 192 if (obsService) { 193 obsService->NotifyObservers(nullptr, "content-removed-cookie", nullptr); 194 } 195 return IPC_OK(); 196 } 197 198 void CookieServiceChild::RemoveSingleCookie(const CookieStruct& aCookie, 199 const OriginAttributes& aAttrs, 200 const Maybe<nsID>& aOperationID) { 201 nsCString baseDomain; 202 if (NS_FAILED(CookieCommons::GetBaseDomainFromHost( 203 mTLDService, aCookie.host(), baseDomain))) { 204 MOZ_ASSERT(false, 205 "CookieServiceChild::RemoveSingleCookie - GetBaseDomainFromHost " 206 "shouldn't fail"); 207 return; 208 } 209 CookieKey key(baseDomain, aAttrs); 210 CookiesList* cookiesList = nullptr; 211 mCookiesMap.Get(key, &cookiesList); 212 213 if (!cookiesList) { 214 return; 215 } 216 217 for (uint32_t i = 0; i < cookiesList->Length(); i++) { 218 RefPtr<Cookie> cookie = cookiesList->ElementAt(i); 219 // bug 1858366: In the case that we are updating a stale cookie 220 // from the content process: the parent process will signal 221 // a batch deletion for the old cookie. 222 // When received by the content process we should not remove 223 // the new cookie since we have already updated the content 224 // process cookies. So we also check the expiry here. 225 if (cookie->Name().Equals(aCookie.name()) && 226 cookie->Host().Equals(aCookie.host()) && 227 cookie->Path().Equals(aCookie.path()) && 228 cookie->ExpiryInMSec() <= aCookie.expiryInMSec()) { 229 cookiesList->RemoveElementAt(i); 230 NotifyObservers(cookie, aAttrs, CookieNotificationAction::CookieDeleted, 231 aOperationID); 232 break; 233 } 234 } 235 } 236 237 IPCResult CookieServiceChild::RecvAddCookie(const CookieStruct& aCookie, 238 const OriginAttributes& aAttrs, 239 const Maybe<nsID>& aOperationID) { 240 RefPtr<Cookie> cookie = Cookie::Create(aCookie, aAttrs); 241 242 CookieNotificationAction action = RecordDocumentCookie(cookie, aAttrs); 243 NotifyObservers(cookie, aAttrs, action, aOperationID); 244 245 // signal test code to check their cookie list 246 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 247 if (obsService) { 248 obsService->NotifyObservers(nullptr, "content-added-cookie", nullptr); 249 } 250 251 return IPC_OK(); 252 } 253 254 IPCResult CookieServiceChild::RecvRemoveBatchDeletedCookies( 255 nsTArray<CookieStruct>&& aCookiesList, 256 nsTArray<OriginAttributes>&& aAttrsList) { 257 MOZ_ASSERT(aCookiesList.Length() == aAttrsList.Length()); 258 for (uint32_t i = 0; i < aCookiesList.Length(); i++) { 259 CookieStruct cookieStruct = aCookiesList.ElementAt(i); 260 RemoveSingleCookie(cookieStruct, aAttrsList.ElementAt(i), Nothing()); 261 } 262 263 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 264 if (obsService) { 265 obsService->NotifyObservers(nullptr, "content-batch-deleted-cookies", 266 nullptr); 267 } 268 return IPC_OK(); 269 } 270 271 IPCResult CookieServiceChild::RecvTrackCookiesLoad( 272 nsTArray<CookieStructTable>&& aCookiesListTable) { 273 for (auto& entry : aCookiesListTable) { 274 for (auto& cookieEntry : entry.cookies()) { 275 RefPtr<Cookie> cookie = Cookie::Create(cookieEntry, entry.attrs()); 276 cookie->SetIsHttpOnly(false); 277 RecordDocumentCookie(cookie, entry.attrs()); 278 } 279 } 280 281 nsCOMPtr<nsIObserverService> obsService = services::GetObserverService(); 282 if (obsService) { 283 obsService->NotifyObservers(nullptr, "content-track-cookies-loaded", 284 nullptr); 285 } 286 287 return IPC_OK(); 288 } 289 290 /* static */ bool CookieServiceChild::RequireThirdPartyCheck( 291 nsILoadInfo* aLoadInfo) { 292 if (!aLoadInfo) { 293 return false; 294 } 295 296 nsCOMPtr<nsICookieJarSettings> cookieJarSettings; 297 nsresult rv = 298 aLoadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); 299 if (NS_WARN_IF(NS_FAILED(rv))) { 300 return false; 301 } 302 303 uint32_t cookieBehavior = cookieJarSettings->GetCookieBehavior(); 304 return cookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN || 305 cookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN || 306 cookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER || 307 cookieBehavior == 308 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN; 309 } 310 311 CookieServiceChild::CookieNotificationAction 312 CookieServiceChild::RecordDocumentCookie(Cookie* aCookie, 313 const OriginAttributes& aAttrs) { 314 nsAutoCString baseDomain; 315 if (NS_FAILED(CookieCommons::GetBaseDomainFromHost( 316 mTLDService, aCookie->Host(), baseDomain))) { 317 MOZ_ASSERT(false, 318 "CookieServiceChild::RecordDocumentCookie - " 319 "GetBaseDomainFromHost shouldn't fail"); 320 return CookieNotificationAction::NoActionNeeded; 321 } 322 323 if (CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS( 324 aCookie, baseDomain, aAttrs)) { 325 COOKIE_LOGSTRING(LogLevel::Error, 326 ("Invalid first-party partitioned cookie without " 327 "partitioned cookie attribution from the document.")); 328 MOZ_ASSERT(false); 329 return CookieNotificationAction::NoActionNeeded; 330 } 331 332 CookieKey key(baseDomain, aAttrs); 333 CookiesList* cookiesList = nullptr; 334 mCookiesMap.Get(key, &cookiesList); 335 336 if (!cookiesList) { 337 cookiesList = mCookiesMap.GetOrInsertNew(key); 338 } 339 340 bool cookieFound = false; 341 342 for (uint32_t i = 0; i < cookiesList->Length(); i++) { 343 Cookie* cookie = cookiesList->ElementAt(i); 344 if (cookie->Name().Equals(aCookie->Name()) && 345 cookie->Host().Equals(aCookie->Host()) && 346 cookie->Path().Equals(aCookie->Path())) { 347 if (cookie->Value().Equals(aCookie->Value()) && 348 cookie->ExpiryInMSec() == aCookie->ExpiryInMSec() && 349 cookie->IsSecure() == aCookie->IsSecure() && 350 cookie->SameSite() == aCookie->SameSite() && 351 cookie->IsSession() == aCookie->IsSession() && 352 cookie->IsHttpOnly() == aCookie->IsHttpOnly()) { 353 cookie->SetLastAccessedInUSec(aCookie->LastAccessedInUSec()); 354 return CookieNotificationAction::NoActionNeeded; 355 } 356 cookiesList->RemoveElementAt(i); 357 cookieFound = true; 358 break; 359 } 360 } 361 362 int64_t currentTimeInMSec = PR_Now() / PR_USEC_PER_MSEC; 363 if (aCookie->ExpiryInMSec() <= currentTimeInMSec) { 364 return cookieFound ? CookieNotificationAction::CookieDeleted 365 : CookieNotificationAction::NoActionNeeded; 366 } 367 368 cookiesList->AppendElement(aCookie); 369 return cookieFound ? CookieNotificationAction::CookieChanged 370 : CookieNotificationAction::CookieAdded; 371 } 372 373 NS_IMETHODIMP 374 CookieServiceChild::GetCookieStringFromHttp(nsIURI* /*aHostURI*/, 375 nsIChannel* /*aChannel*/, 376 nsACString& /*aCookieString*/) { 377 MOZ_CRASH("This method should not be called"); 378 return NS_ERROR_NOT_IMPLEMENTED; 379 } 380 381 NS_IMETHODIMP 382 CookieServiceChild::SetCookieStringFromHttp(nsIURI* aHostURI, 383 const nsACString& aCookieString, 384 nsIChannel* aChannel) { 385 MOZ_CRASH("This method should not be called"); 386 return NS_ERROR_NOT_IMPLEMENTED; 387 } 388 389 NS_IMETHODIMP 390 CookieServiceChild::RunInTransaction( 391 nsICookieTransactionCallback* /*aCallback*/) { 392 return NS_ERROR_NOT_IMPLEMENTED; 393 } 394 395 void CookieServiceChild::GetCookiesFromHost( 396 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes, 397 nsTArray<RefPtr<Cookie>>& aCookies) { 398 CookieKey key(aBaseDomain, aOriginAttributes); 399 400 CookiesList* cookiesList = nullptr; 401 mCookiesMap.Get(key, &cookiesList); 402 403 if (cookiesList) { 404 aCookies.AppendElements(*cookiesList); 405 } 406 } 407 408 void CookieServiceChild::StaleCookies(const nsTArray<RefPtr<Cookie>>& aCookies, 409 int64_t aCurrentTimeInUsec) { 410 // Nothing to do here. 411 } 412 413 bool CookieServiceChild::HasExistingCookies( 414 const nsACString& aBaseDomain, const OriginAttributes& aOriginAttributes) { 415 CookiesList* cookiesList = nullptr; 416 417 CookieKey key(aBaseDomain, aOriginAttributes); 418 mCookiesMap.Get(key, &cookiesList); 419 420 return cookiesList ? cookiesList->Length() : 0; 421 } 422 423 void CookieServiceChild::AddCookieFromDocument( 424 CookieParser& aCookieParser, const nsACString& aBaseDomain, 425 const OriginAttributes& aOriginAttributes, Cookie& aCookie, 426 int64_t aCurrentTimeInUsec, nsIURI* aDocumentURI, bool aThirdParty, 427 dom::Document* aDocument) { 428 MOZ_ASSERT(aDocumentURI); 429 MOZ_ASSERT(aDocument); 430 431 CookieKey key(aBaseDomain, aOriginAttributes); 432 CookiesList* cookies = mCookiesMap.Get(key); 433 434 if (cookies) { 435 // We need to see if the cookie we're setting would overwrite an httponly 436 // or a secure one. This would not affect anything we send over the net 437 // (those come from the parent, which already checks this), 438 // but script could see an inconsistent view of things. 439 440 // CHIPS - If the cookie has the "Partitioned" attribute set it will be 441 // stored in the partitioned cookie jar. 442 bool needPartitioned = StaticPrefs::network_cookie_CHIPS_enabled() && 443 aCookie.RawIsPartitioned(); 444 nsCOMPtr<nsIPrincipal> principal = 445 needPartitioned ? aDocument->PartitionedPrincipal() 446 : aDocument->EffectiveCookiePrincipal(); 447 bool isPotentiallyTrustworthy = 448 principal->GetIsOriginPotentiallyTrustworthy(); 449 450 for (uint32_t i = 0; i < cookies->Length(); ++i) { 451 RefPtr<Cookie> existingCookie = cookies->ElementAt(i); 452 if (existingCookie->Name().Equals(aCookie.Name()) && 453 existingCookie->Host().Equals(aCookie.Host()) && 454 existingCookie->Path().Equals(aCookie.Path())) { 455 // Can't overwrite an httponly cookie from a script context. 456 if (existingCookie->IsHttpOnly()) { 457 return; 458 } 459 460 // prevent insecure cookie from overwriting a secure one in insecure 461 // context. 462 if (existingCookie->IsSecure() && !isPotentiallyTrustworthy) { 463 return; 464 } 465 } 466 } 467 } 468 469 CookieNotificationAction action = 470 RecordDocumentCookie(&aCookie, aOriginAttributes); 471 NotifyObservers(&aCookie, aOriginAttributes, action); 472 473 if (CanSend()) { 474 nsTArray<CookieStruct> cookiesToSend; 475 cookiesToSend.AppendElement(aCookie.ToIPC()); 476 477 // Asynchronously call the parent. 478 dom::WindowGlobalChild* windowGlobalChild = 479 aDocument->GetWindowGlobalChild(); 480 481 // If there is no WindowGlobalChild fall back to PCookieService SetCookies. 482 if (NS_WARN_IF(!windowGlobalChild)) { 483 SendSetCookies(aBaseDomain, aOriginAttributes, aDocumentURI, false, 484 aThirdParty, cookiesToSend); 485 return; 486 } 487 488 windowGlobalChild->SendSetCookies(aBaseDomain, aOriginAttributes, 489 aDocumentURI, false, aThirdParty, 490 cookiesToSend); 491 } 492 } 493 494 void CookieServiceChild::NotifyObservers(Cookie* aCookie, 495 const OriginAttributes& aAttrs, 496 CookieNotificationAction aAction, 497 const Maybe<nsID>& aOperationID) { 498 nsICookieNotification::Action notificationAction; 499 switch (aAction) { 500 case CookieNotificationAction::NoActionNeeded: 501 return; 502 503 case CookieNotificationAction::CookieAdded: 504 notificationAction = nsICookieNotification::COOKIE_ADDED; 505 break; 506 507 case CookieNotificationAction::CookieChanged: 508 notificationAction = nsICookieNotification::COOKIE_CHANGED; 509 break; 510 511 case CookieNotificationAction::CookieDeleted: 512 notificationAction = nsICookieNotification::COOKIE_DELETED; 513 break; 514 } 515 516 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 517 if (!os) { 518 return; 519 } 520 521 nsAutoCString baseDomain; 522 if (NS_FAILED(CookieCommons::GetBaseDomainFromHost( 523 mTLDService, aCookie->Host(), baseDomain))) { 524 MOZ_ASSERT(false, 525 "CookieServiceChild::NotifyObservers - GetBaseDomainFromHost " 526 "shouldn't fail"); 527 return; 528 } 529 530 nsCOMPtr<nsICookieNotification> notification = 531 new CookieNotification(notificationAction, aCookie, baseDomain, false, 532 nullptr, 0, aOperationID.ptrOr(nullptr)); 533 534 os->NotifyObservers( 535 notification, 536 aAttrs.IsPrivateBrowsing() ? "private-cookie-changed" : "cookie-changed", 537 u""); 538 } 539 540 } // namespace net 541 } // namespace mozilla