CookieCommons.cpp (36459B)
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 "CookieParser.h" 10 #include "CookieService.h" 11 #include "mozilla/ContentBlockingNotifier.h" 12 #include "mozilla/StaticPrefs_network.h" 13 #include "mozilla/StorageAccess.h" 14 #include "mozilla/dom/nsMixedContentBlocker.h" 15 #include "mozilla/dom/CanonicalBrowsingContext.h" 16 #include "mozilla/dom/Document.h" 17 #include "mozilla/dom/WindowGlobalParent.h" 18 #include "mozilla/dom/WorkerCommon.h" 19 #include "mozilla/dom/WorkerPrivate.h" 20 #include "mozilla/net/CookieJarSettings.h" 21 #include "mozIThirdPartyUtil.h" 22 #include "nsContentUtils.h" 23 #include "nsICookiePermission.h" 24 #include "nsICookieService.h" 25 #include "nsIEffectiveTLDService.h" 26 #include "nsIGlobalObject.h" 27 #include "nsIHttpChannel.h" 28 #include "nsIRedirectHistoryEntry.h" 29 #include "nsIWebProgressListener.h" 30 #include "nsNetUtil.h" 31 #include "nsSandboxFlags.h" 32 #include "nsScriptSecurityManager.h" 33 #include "nsReadableUtils.h" 34 #include "ThirdPartyUtil.h" 35 36 namespace mozilla { 37 38 using dom::Document; 39 40 namespace net { 41 42 // static 43 bool CookieCommons::DomainMatches(Cookie* aCookie, const nsACString& aHost) { 44 // first, check for an exact host or domain cookie match, e.g. "google.com" 45 // or ".google.com"; second a subdomain match, e.g. 46 // host = "mail.google.com", cookie domain = ".google.com". 47 return aCookie->RawHost() == aHost || 48 (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host())); 49 } 50 51 // static 52 bool CookieCommons::PathMatches(Cookie* aCookie, const nsACString& aPath) { 53 return PathMatches(aCookie->Path(), aPath); 54 } 55 56 // static 57 bool CookieCommons::PathMatches(const nsACString& aCookiePath, 58 const nsACString& aPath) { 59 // if our cookie path is empty we can't really perform our prefix check, and 60 // also we can't check the last character of the cookie path, so we would 61 // never return a successful match. 62 if (aCookiePath.IsEmpty()) { 63 return false; 64 } 65 66 // if the cookie path and the request path are identical, they match. 67 if (aCookiePath.Equals(aPath)) { 68 return true; 69 } 70 71 // if the cookie path is a prefix of the request path, and the last character 72 // of the cookie path is %x2F ("/"), they match. 73 bool isPrefix = StringBeginsWith(aPath, aCookiePath); 74 if (isPrefix && aCookiePath.Last() == '/') { 75 return true; 76 } 77 78 // if the cookie path is a prefix of the request path, and the first character 79 // of the request path that is not included in the cookie path is a %x2F ("/") 80 // character, they match. 81 uint32_t cookiePathLen = aCookiePath.Length(); 82 return isPrefix && aPath[cookiePathLen] == '/'; 83 } 84 85 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be 86 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing 87 // dot may be present. If aHostURI is an IP address, an alias such as 88 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will 89 // be the exact host, and aRequireHostMatch will be true to indicate that 90 // substring matches should not be performed. 91 nsresult CookieCommons::GetBaseDomain(nsIEffectiveTLDService* aTLDService, 92 nsIURI* aHostURI, nsACString& aBaseDomain, 93 bool& aRequireHostMatch) { 94 // get the base domain. this will fail if the host contains a leading dot, 95 // more than one trailing dot, or is otherwise malformed. 96 nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); 97 aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS || 98 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS; 99 if (aRequireHostMatch) { 100 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD 101 // such as 'co.uk', or the empty string. use the host as a key in such 102 // cases. 103 rv = nsContentUtils::GetHostOrIPv6WithBrackets(aHostURI, aBaseDomain); 104 } 105 NS_ENSURE_SUCCESS(rv, rv); 106 107 // aHost (and thus aBaseDomain) may be the string '.'. If so, fail. 108 if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') { 109 return NS_ERROR_INVALID_ARG; 110 } 111 112 // block any URIs without a host that aren't file:// URIs. 113 if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) { 114 return NS_ERROR_INVALID_ARG; 115 } 116 117 return NS_OK; 118 } 119 120 nsresult CookieCommons::GetBaseDomain(nsIPrincipal* aPrincipal, 121 nsACString& aBaseDomain) { 122 MOZ_ASSERT(aPrincipal); 123 124 // for historical reasons we use ascii host for file:// URLs. 125 if (aPrincipal->SchemeIs("file")) { 126 return nsContentUtils::GetHostOrIPv6WithBrackets(aPrincipal, aBaseDomain); 127 } 128 129 nsresult rv = aPrincipal->GetBaseDomain(aBaseDomain); 130 if (NS_FAILED(rv)) { 131 return rv; 132 } 133 134 nsContentUtils::MaybeFixIPv6Host(aBaseDomain); 135 return NS_OK; 136 } 137 138 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be 139 // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it 140 // is assumed that aHost is already normalized, and it may contain a leading dot 141 // (indicating that it represents a domain). A trailing dot may be present. 142 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as 143 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a 144 // leading dot will be treated as an error. 145 nsresult CookieCommons::GetBaseDomainFromHost( 146 nsIEffectiveTLDService* aTLDService, const nsACString& aHost, 147 nsCString& aBaseDomain) { 148 // aHost must not be the string '.'. 149 if (aHost.Length() == 1 && aHost.Last() == '.') { 150 return NS_ERROR_INVALID_ARG; 151 } 152 153 // aHost may contain a leading dot; if so, strip it now. 154 bool domain = !aHost.IsEmpty() && aHost.First() == '.'; 155 156 // get the base domain. this will fail if the host contains a leading dot, 157 // more than one trailing dot, or is otherwise malformed. 158 nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, 159 aBaseDomain); 160 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || 161 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { 162 // aHost is either an IP address, an alias such as 'localhost', an eTLD 163 // such as 'co.uk', or the empty string. use the host as a key in such 164 // cases; however, we reject any such hosts with a leading dot, since it 165 // doesn't make sense for them to be domain cookies. 166 if (domain) { 167 return NS_ERROR_INVALID_ARG; 168 } 169 170 aBaseDomain = aHost; 171 return NS_OK; 172 } 173 return rv; 174 } 175 176 /* static */ bool CookieCommons::IsIPv6BaseDomain( 177 const nsACString& aBaseDomain) { 178 return aBaseDomain.Contains(':'); 179 } 180 181 namespace { 182 183 void NotifyRejectionToObservers(nsIURI* aHostURI, CookieOperation aOperation) { 184 if (aOperation == OPERATION_WRITE) { 185 nsCOMPtr<nsIObserverService> os = services::GetObserverService(); 186 if (os) { 187 os->NotifyObservers(aHostURI, "cookie-rejected", nullptr); 188 } 189 } else { 190 MOZ_ASSERT(aOperation == OPERATION_READ); 191 } 192 } 193 194 } // namespace 195 196 // Notify observers that a cookie was rejected due to the users' prefs. 197 void CookieCommons::NotifyRejected(nsIURI* aHostURI, nsIChannel* aChannel, 198 uint32_t aRejectedReason, 199 CookieOperation aOperation) { 200 NotifyRejectionToObservers(aHostURI, aOperation); 201 202 ContentBlockingNotifier::OnDecision( 203 aChannel, ContentBlockingNotifier::BlockingDecision::eBlock, 204 aRejectedReason); 205 } 206 207 // static 208 bool CookieCommons::CheckCookiePermission(nsIChannel* aChannel, 209 CookieStruct& aCookieData) { 210 if (!aChannel) { 211 // No channel, let's assume this is a system-principal request. 212 return true; 213 } 214 215 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 216 nsCOMPtr<nsICookieJarSettings> cookieJarSettings; 217 nsresult rv = 218 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); 219 if (NS_WARN_IF(NS_FAILED(rv))) { 220 return true; 221 } 222 223 nsIScriptSecurityManager* ssm = 224 nsScriptSecurityManager::GetScriptSecurityManager(); 225 MOZ_ASSERT(ssm); 226 227 nsCOMPtr<nsIPrincipal> channelPrincipal; 228 rv = ssm->GetChannelURIPrincipal(aChannel, getter_AddRefs(channelPrincipal)); 229 if (NS_WARN_IF(NS_FAILED(rv))) { 230 return false; 231 } 232 233 return CheckCookiePermission(channelPrincipal, cookieJarSettings, 234 aCookieData); 235 } 236 237 // static 238 bool CookieCommons::CheckCookiePermission( 239 nsIPrincipal* aPrincipal, nsICookieJarSettings* aCookieJarSettings, 240 CookieStruct& aCookieData) { 241 MOZ_ASSERT(aPrincipal); 242 MOZ_ASSERT(aCookieJarSettings); 243 244 if (!aPrincipal->GetIsContentPrincipal()) { 245 return true; 246 } 247 248 uint32_t cookiePermission = nsICookiePermission::ACCESS_DEFAULT; 249 nsresult rv = 250 aCookieJarSettings->CookiePermission(aPrincipal, &cookiePermission); 251 if (NS_WARN_IF(NS_FAILED(rv))) { 252 return true; 253 } 254 255 if (cookiePermission == nsICookiePermission::ACCESS_ALLOW) { 256 return true; 257 } 258 259 if (cookiePermission == nsICookiePermission::ACCESS_SESSION) { 260 aCookieData.isSession() = true; 261 return true; 262 } 263 264 if (cookiePermission == nsICookiePermission::ACCESS_DENY) { 265 return false; 266 } 267 268 return true; 269 } 270 271 namespace { 272 273 CookieStatus CookieStatusForWindow(nsPIDOMWindowInner* aWindow, 274 nsIURI* aDocumentURI) { 275 MOZ_ASSERT(aWindow); 276 MOZ_ASSERT(aDocumentURI); 277 278 ThirdPartyUtil* thirdPartyUtil = ThirdPartyUtil::GetInstance(); 279 if (thirdPartyUtil) { 280 bool isThirdParty = true; 281 282 nsresult rv = thirdPartyUtil->IsThirdPartyWindow( 283 aWindow->GetOuterWindow(), aDocumentURI, &isThirdParty); 284 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Third-party window check failed."); 285 286 if (NS_SUCCEEDED(rv) && !isThirdParty) { 287 return STATUS_ACCEPTED; 288 } 289 } 290 291 return STATUS_ACCEPTED; 292 } 293 294 bool CheckCookieStringFromDocument(const nsACString& aCookieString) { 295 // If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F character (CTL 296 // characters excluding HTAB): Abort these steps and ignore the 297 // set-cookie-string entirely. 298 const char illegalCharacters[] = { 299 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x0A, 0x0B, 0x0C, 300 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 301 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x7F, 0x00}; 302 303 const auto* start = aCookieString.BeginReading(); 304 const auto* end = aCookieString.EndReading(); 305 306 auto charFilter = [&](unsigned char c) { 307 if (StaticPrefs::network_cookie_blockUnicode() && c >= 0x80) { 308 return true; 309 } 310 return std::find(std::begin(illegalCharacters), std::end(illegalCharacters), 311 c) != std::end(illegalCharacters); 312 }; 313 314 return std::find_if(start, end, charFilter) == end; 315 } 316 317 } // namespace 318 319 // static 320 already_AddRefed<Cookie> CookieCommons::CreateCookieFromDocument( 321 CookieParser& aCookieParser, Document* aDocument, 322 const nsACString& aCookieString, int64_t currentTimeInUsec, 323 nsIEffectiveTLDService* aTLDService, mozIThirdPartyUtil* aThirdPartyUtil, 324 nsACString& aBaseDomain, OriginAttributes& aAttrs) { 325 if (!CookieCommons::IsSchemeSupported(aCookieParser.HostURI())) { 326 return nullptr; 327 } 328 329 if (!CheckCookieStringFromDocument(aCookieString)) { 330 return nullptr; 331 } 332 333 nsAutoCString baseDomain; 334 bool requireHostMatch = false; 335 nsresult rv = CookieCommons::GetBaseDomain( 336 aTLDService, aCookieParser.HostURI(), baseDomain, requireHostMatch); 337 if (NS_WARN_IF(NS_FAILED(rv))) { 338 return nullptr; 339 } 340 341 nsPIDOMWindowInner* innerWindow = aDocument->GetInnerWindow(); 342 if (NS_WARN_IF(!innerWindow)) { 343 return nullptr; 344 } 345 346 bool isForeignAndNotAddon = false; 347 if (!BasePrincipal::Cast(aDocument->NodePrincipal())->AddonPolicy()) { 348 rv = aThirdPartyUtil->IsThirdPartyWindow(innerWindow->GetOuterWindow(), 349 aCookieParser.HostURI(), 350 &isForeignAndNotAddon); 351 if (NS_WARN_IF(NS_FAILED(rv))) { 352 isForeignAndNotAddon = true; 353 } 354 } 355 356 bool mustBePartitioned = 357 isForeignAndNotAddon && 358 aDocument->CookieJarSettings()->GetCookieBehavior() == 359 nsICookieService::BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN && 360 !aDocument->UsingStorageAccess(); 361 362 // If we are here, we have been already accepted by the anti-tracking. 363 // We just need to check if we have to be in session-only mode. 364 CookieStatus cookieStatus = 365 CookieStatusForWindow(innerWindow, aCookieParser.HostURI()); 366 MOZ_ASSERT(cookieStatus == STATUS_ACCEPTED || 367 cookieStatus == STATUS_ACCEPT_SESSION); 368 369 nsCString cookieString(aCookieString); 370 371 nsCOMPtr<nsILoadInfo> loadInfo = 372 aDocument->GetChannel() ? aDocument->GetChannel()->LoadInfo() : nullptr; 373 const bool on3pcbException = loadInfo && loadInfo->GetIsOn3PCBExceptionList(); 374 375 aCookieParser.Parse(baseDomain, requireHostMatch, cookieStatus, cookieString, 376 EmptyCString(), false, isForeignAndNotAddon, 377 mustBePartitioned, aDocument->IsInPrivateBrowsing(), 378 on3pcbException, PR_Now()); 379 380 if (!aCookieParser.ContainsCookie()) { 381 return nullptr; 382 } 383 384 // check permissions from site permission list. 385 if (!CookieCommons::CheckCookiePermission(aDocument->NodePrincipal(), 386 aDocument->CookieJarSettings(), 387 aCookieParser.CookieData())) { 388 NotifyRejectionToObservers(aCookieParser.HostURI(), OPERATION_WRITE); 389 ContentBlockingNotifier::OnDecision( 390 innerWindow, ContentBlockingNotifier::BlockingDecision::eBlock, 391 nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION); 392 return nullptr; 393 } 394 395 // CHIPS - If the partitioned attribute is set, store cookie in partitioned 396 // cookie jar independent of context. If the cookies are stored in the 397 // partitioned cookie jar anyway no special treatment of CHIPS cookies 398 // necessary. 399 bool needPartitioned = StaticPrefs::network_cookie_CHIPS_enabled() && 400 aCookieParser.CookieData().isPartitioned(); 401 nsCOMPtr<nsIPrincipal> cookiePrincipal = 402 needPartitioned ? aDocument->PartitionedPrincipal() 403 : aDocument->EffectiveCookiePrincipal(); 404 MOZ_ASSERT(cookiePrincipal); 405 406 nsCOMPtr<nsICookieService> service = 407 do_GetService(NS_COOKIESERVICE_CONTRACTID); 408 if (!service) { 409 return nullptr; 410 } 411 412 // Check if limit-foreign is required. 413 uint32_t dummyRejectedReason = 0; 414 if (aDocument->CookieJarSettings()->GetLimitForeignContexts() && 415 !service->HasExistingCookies(baseDomain, 416 cookiePrincipal->OriginAttributesRef()) && 417 !ShouldAllowAccessFor(innerWindow, aCookieParser.HostURI(), true, 418 &dummyRejectedReason)) { 419 return nullptr; 420 } 421 422 RefPtr<Cookie> cookie = Cookie::Create( 423 aCookieParser.CookieData(), cookiePrincipal->OriginAttributesRef()); 424 MOZ_ASSERT(cookie); 425 426 cookie->SetLastAccessedInUSec(currentTimeInUsec); 427 cookie->SetCreationTimeInUSec( 428 Cookie::GenerateUniqueCreationTimeInUSec(currentTimeInUsec)); 429 cookie->SetUpdateTimeInUSec(cookie->CreationTimeInUSec()); 430 431 aBaseDomain = baseDomain; 432 aAttrs = cookiePrincipal->OriginAttributesRef(); 433 434 return cookie.forget(); 435 } 436 437 // static 438 already_AddRefed<nsICookieJarSettings> CookieCommons::GetCookieJarSettings( 439 nsIChannel* aChannel) { 440 nsCOMPtr<nsICookieJarSettings> cookieJarSettings; 441 bool shouldResistFingerprinting = nsContentUtils::ShouldResistFingerprinting( 442 aChannel, RFPTarget::IsAlwaysEnabledForPrecompute); 443 if (aChannel) { 444 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 445 nsresult rv = 446 loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); 447 if (NS_WARN_IF(NS_FAILED(rv))) { 448 cookieJarSettings = 449 CookieJarSettings::GetBlockingAll(shouldResistFingerprinting); 450 } 451 } else { 452 cookieJarSettings = CookieJarSettings::Create(CookieJarSettings::eRegular, 453 shouldResistFingerprinting); 454 } 455 456 MOZ_ASSERT(cookieJarSettings); 457 return cookieJarSettings.forget(); 458 } 459 460 // static 461 bool CookieCommons::ShouldIncludeCrossSiteCookie( 462 Cookie* aCookie, nsIURI* aHostURI, bool aPartitionForeign, 463 bool aInPrivateBrowsing, bool aUsingStorageAccess, bool aOn3pcbException) { 464 MOZ_ASSERT(aCookie); 465 466 int32_t sameSiteAttr = 0; 467 aCookie->GetSameSite(&sameSiteAttr); 468 469 return ShouldIncludeCrossSiteCookie( 470 aHostURI, sameSiteAttr, 471 aCookie->IsPartitioned() && aCookie->RawIsPartitioned(), 472 aPartitionForeign, aInPrivateBrowsing, aUsingStorageAccess, 473 aOn3pcbException); 474 } 475 476 // static 477 bool CookieCommons::ShouldIncludeCrossSiteCookie( 478 nsIURI* aHostURI, int32_t aSameSiteAttr, bool aCookiePartitioned, 479 bool aPartitionForeign, bool aInPrivateBrowsing, bool aUsingStorageAccess, 480 bool aOn3pcbException) { 481 if (aSameSiteAttr == nsICookie::SAMESITE_UNSET) { 482 bool laxByDefault = 483 StaticPrefs::network_cookie_sameSite_laxByDefault() && 484 !nsContentUtils::IsURIInPrefList( 485 aHostURI, "network.cookie.sameSite.laxByDefault.disabledHosts"); 486 aSameSiteAttr = 487 laxByDefault ? nsICookie::SAMESITE_LAX : nsICookie::SAMESITE_NONE; 488 } 489 490 // CHIPS - If a third-party has storage access it can access both it's 491 // partitioned and unpartitioned cookie jars, else its cookies are blocked. 492 // 493 // Note that we will only include partitioned cookies that have "partitioned" 494 // attribution if we enable opt-in partitioning. 495 if (aPartitionForeign && 496 (StaticPrefs::network_cookie_cookieBehavior_optInPartitioning() || 497 (aInPrivateBrowsing && 498 StaticPrefs:: 499 network_cookie_cookieBehavior_optInPartitioning_pbmode())) && 500 !aCookiePartitioned && !aUsingStorageAccess && !aOn3pcbException) { 501 return false; 502 } 503 504 return aSameSiteAttr == nsICookie::SAMESITE_NONE; 505 } 506 507 // static 508 bool CookieCommons::IsFirstPartyPartitionedCookieWithoutCHIPS( 509 Cookie* aCookie, const nsACString& aBaseDomain, 510 const OriginAttributes& aOriginAttributes) { 511 MOZ_ASSERT(aCookie); 512 513 // The cookie is set with partitioned attribute. This is a CHIPS cookies. 514 if (aCookie->RawIsPartitioned()) { 515 return false; 516 } 517 518 // The originAttributes is not partitioned. This is not a partitioned cookie. 519 if (aOriginAttributes.mPartitionKey.IsEmpty()) { 520 return false; 521 } 522 523 nsAutoString scheme; 524 nsAutoString baseDomain; 525 int32_t port; 526 bool foreignByAncestorContext; 527 // Bail out early if the partition key is not valid. 528 if (!OriginAttributes::ParsePartitionKey(aOriginAttributes.mPartitionKey, 529 scheme, baseDomain, port, 530 foreignByAncestorContext)) { 531 return false; 532 } 533 534 // Check whether the base domain of the cookie match the base domain in the 535 // partitionKey and it is not an ABA context 536 return aBaseDomain.Equals(NS_ConvertUTF16toUTF8(baseDomain)) && 537 !foreignByAncestorContext; 538 } 539 540 // static 541 bool CookieCommons::ShouldEnforceSessionForOriginAttributes( 542 const OriginAttributes& aOriginAttributes) { 543 // We don't need to enforce session for unpartitioned OAs. 544 if (aOriginAttributes.mPartitionKey.IsEmpty()) { 545 return false; 546 } 547 548 // We enforce session cookies if the partitionKey is from a null principal. 549 // This ensures that we don't create dangling cookies that cannot be deleted. 550 // A partitionKey from a null principal ends with ".mozilla". 551 if (StringEndsWith(aOriginAttributes.mPartitionKey, u".mozilla"_ns)) { 552 return true; 553 } 554 555 return false; 556 } 557 558 bool CookieCommons::IsSafeTopLevelNav(nsIChannel* aChannel) { 559 if (!aChannel) { 560 return false; 561 } 562 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 563 nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo(); 564 if ((loadInfo->GetExternalContentPolicyType() != 565 ExtContentPolicy::TYPE_DOCUMENT && 566 loadInfo->GetExternalContentPolicyType() != 567 ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) && 568 !interceptionInfo) { 569 return false; 570 } 571 572 if (interceptionInfo && 573 interceptionInfo->GetExtContentPolicyType() != 574 ExtContentPolicy::TYPE_DOCUMENT && 575 interceptionInfo->GetExtContentPolicyType() != 576 ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD && 577 interceptionInfo->GetExtContentPolicyType() != 578 ExtContentPolicy::TYPE_INVALID) { 579 return false; 580 } 581 582 return NS_IsSafeMethodNav(aChannel); 583 } 584 585 // This function determines if two schemes are equal in the context of 586 // "Schemeful SameSite cookies". 587 // 588 // Two schemes are considered equal: 589 // - if the "network.cookie.sameSite.schemeful" pref is set to false. 590 // OR 591 // - if one of the schemes is not http or https. 592 // OR 593 // - if both schemes are equal AND both are either http or https. 594 bool IsSameSiteSchemeEqual(const nsACString& aFirstScheme, 595 const nsACString& aSecondScheme) { 596 if (!StaticPrefs::network_cookie_sameSite_schemeful()) { 597 return true; 598 } 599 600 auto isSchemeHttpOrHttps = [](const nsACString& scheme) -> bool { 601 return scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https"); 602 }; 603 604 if (!isSchemeHttpOrHttps(aFirstScheme) || 605 !isSchemeHttpOrHttps(aSecondScheme)) { 606 return true; 607 } 608 609 return aFirstScheme.Equals(aSecondScheme); 610 } 611 612 bool CookieCommons::IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI, 613 bool* aHadCrossSiteRedirects) { 614 *aHadCrossSiteRedirects = false; 615 616 if (!aChannel) { 617 return false; 618 } 619 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 620 // Do not treat loads triggered by web extensions as foreign 621 nsCOMPtr<nsIURI> channelURI; 622 NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); 623 624 nsCOMPtr<nsIInterceptionInfo> interceptionInfo = loadInfo->InterceptionInfo(); 625 626 RefPtr<BasePrincipal> triggeringPrincipal; 627 ExtContentPolicy contentPolicyType; 628 if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) { 629 triggeringPrincipal = 630 BasePrincipal::Cast(interceptionInfo->TriggeringPrincipal()); 631 contentPolicyType = interceptionInfo->GetExtContentPolicyType(); 632 } else { 633 triggeringPrincipal = BasePrincipal::Cast(loadInfo->TriggeringPrincipal()); 634 contentPolicyType = loadInfo->GetExternalContentPolicyType(); 635 636 if (triggeringPrincipal->AddonPolicy() && 637 triggeringPrincipal->AddonAllowsLoad(channelURI)) { 638 return false; 639 } 640 } 641 const nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>>& redirectChain( 642 interceptionInfo && interceptionInfo->TriggeringPrincipal() 643 ? interceptionInfo->RedirectChain() 644 : loadInfo->RedirectChain()); 645 646 nsAutoCString hostScheme, otherScheme; 647 aHostURI->GetScheme(hostScheme); 648 649 bool isForeign = true; 650 nsresult rv; 651 if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT || 652 contentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) { 653 // for loads of TYPE_DOCUMENT we query the hostURI from the 654 // triggeringPrincipal which returns the URI of the document that caused the 655 // navigation. 656 rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign); 657 658 triggeringPrincipal->GetScheme(otherScheme); 659 } else { 660 // If the load is caused by FetchEvent.request or NavigationPreload request, 661 // check the original InterceptedHttpChannel is a third-party channel or 662 // not. 663 if (interceptionInfo && interceptionInfo->TriggeringPrincipal()) { 664 isForeign = interceptionInfo->FromThirdParty(); 665 if (isForeign) { 666 return true; 667 } 668 } 669 nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = 670 do_GetService(THIRDPARTYUTIL_CONTRACTID); 671 if (!thirdPartyUtil) { 672 return true; 673 } 674 rv = thirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); 675 676 channelURI->GetScheme(otherScheme); 677 } 678 // if we are dealing with a cross origin request, we can return here 679 // because we already know the request is 'foreign'. 680 if (NS_FAILED(rv) || isForeign) { 681 return true; 682 } 683 684 if (!IsSameSiteSchemeEqual(otherScheme, hostScheme)) { 685 // If the two schemes are not of the same http(s) scheme then we 686 // consider the request as foreign. 687 return true; 688 } 689 690 // for loads of TYPE_SUBDOCUMENT we have to perform an additional test, 691 // because a cross-origin iframe might perform a navigation to a same-origin 692 // iframe which would send same-site cookies. Hence, if the iframe navigation 693 // was triggered by a cross-origin triggeringPrincipal, we treat the load as 694 // foreign. 695 if (contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) { 696 rv = triggeringPrincipal->IsThirdPartyChannel(aChannel, &isForeign); 697 if (NS_FAILED(rv) || isForeign) { 698 return true; 699 } 700 } 701 702 // for the purpose of same-site cookies we have to treat any cross-origin 703 // redirects as foreign. E.g. cross-site to same-site redirect is a problem 704 // with regards to CSRF. 705 706 nsCOMPtr<nsIPrincipal> redirectPrincipal; 707 for (nsIRedirectHistoryEntry* entry : redirectChain) { 708 entry->GetPrincipal(getter_AddRefs(redirectPrincipal)); 709 if (redirectPrincipal) { 710 rv = redirectPrincipal->IsThirdPartyChannel(aChannel, &isForeign); 711 // if at any point we encounter a cross-origin redirect we can return. 712 if (NS_FAILED(rv) || isForeign) { 713 *aHadCrossSiteRedirects = isForeign; 714 return true; 715 } 716 717 nsAutoCString redirectScheme; 718 redirectPrincipal->GetScheme(redirectScheme); 719 if (!IsSameSiteSchemeEqual(redirectScheme, hostScheme)) { 720 // If the two schemes are not of the same http(s) scheme then we 721 // consider the request as foreign. 722 *aHadCrossSiteRedirects = true; 723 return true; 724 } 725 } 726 } 727 return isForeign; 728 } 729 730 // static 731 nsICookie::schemeType CookieCommons::URIToSchemeType(nsIURI* aURI) { 732 MOZ_ASSERT(aURI); 733 734 nsAutoCString scheme; 735 nsresult rv = aURI->GetScheme(scheme); 736 if (NS_WARN_IF(NS_FAILED(rv))) { 737 return nsICookie::SCHEME_UNSET; 738 } 739 740 return SchemeToSchemeType(scheme); 741 } 742 743 // static 744 nsICookie::schemeType CookieCommons::PrincipalToSchemeType( 745 nsIPrincipal* aPrincipal) { 746 MOZ_ASSERT(aPrincipal); 747 748 nsAutoCString scheme; 749 nsresult rv = aPrincipal->GetScheme(scheme); 750 if (NS_WARN_IF(NS_FAILED(rv))) { 751 return nsICookie::SCHEME_UNSET; 752 } 753 754 return SchemeToSchemeType(scheme); 755 } 756 757 // static 758 nsICookie::schemeType CookieCommons::SchemeToSchemeType( 759 const nsACString& aScheme) { 760 MOZ_ASSERT(IsSchemeSupported(aScheme)); 761 762 if (aScheme.Equals("https")) { 763 return nsICookie::SCHEME_HTTPS; 764 } 765 766 if (aScheme.Equals("http")) { 767 return nsICookie::SCHEME_HTTP; 768 } 769 770 if (aScheme.Equals("file")) { 771 return nsICookie::SCHEME_FILE; 772 } 773 774 MOZ_CRASH("Unsupported scheme type"); 775 } 776 777 // static 778 bool CookieCommons::IsSchemeSupported(nsIPrincipal* aPrincipal) { 779 MOZ_ASSERT(aPrincipal); 780 781 nsAutoCString scheme; 782 nsresult rv = aPrincipal->GetScheme(scheme); 783 if (NS_WARN_IF(NS_FAILED(rv))) { 784 return false; 785 } 786 787 return IsSchemeSupported(scheme); 788 } 789 790 // static 791 bool CookieCommons::IsSchemeSupported(nsIURI* aURI) { 792 MOZ_ASSERT(aURI); 793 794 nsAutoCString scheme; 795 nsresult rv = aURI->GetScheme(scheme); 796 if (NS_WARN_IF(NS_FAILED(rv))) { 797 return false; 798 } 799 800 return IsSchemeSupported(scheme); 801 } 802 803 // static 804 bool CookieCommons::IsSchemeSupported(const nsACString& aScheme) { 805 return aScheme.Equals("https") || aScheme.Equals("http") || 806 aScheme.Equals("file"); 807 } 808 809 // static 810 bool CookieCommons::ChipsLimitEnabledAndChipsCookie( 811 const Cookie& cookie, dom::BrowsingContext* aBrowsingContext) { 812 bool tcpEnabled = false; 813 if (aBrowsingContext) { 814 dom::CanonicalBrowsingContext* canonBC = aBrowsingContext->Canonical(); 815 if (canonBC) { 816 dom::WindowGlobalParent* windowParent = canonBC->GetCurrentWindowGlobal(); 817 if (windowParent) { 818 nsCOMPtr<nsICookieJarSettings> cjs = windowParent->CookieJarSettings(); 819 tcpEnabled = cjs->GetPartitionForeign(); 820 } 821 } 822 } else { 823 // calls coming from addNative have no document, channel or browsingContext 824 // to determine if TCP is enabled, so we just create a cookieJarSettings to 825 // check the pref. 826 nsCOMPtr<nsICookieJarSettings> cjs; 827 cjs = CookieJarSettings::Create(CookieJarSettings::eRegular, false); 828 tcpEnabled = cjs->GetPartitionForeign(); 829 } 830 831 return StaticPrefs::network_cookie_CHIPS_enabled() && 832 StaticPrefs::network_cookie_chips_partitionLimitEnabled() && 833 cookie.IsPartitioned() && cookie.RawIsPartitioned() && tcpEnabled; 834 } 835 836 void CookieCommons::ComposeCookieString(nsTArray<RefPtr<Cookie>>& aCookieList, 837 nsACString& aCookieString) { 838 for (Cookie* cookie : aCookieList) { 839 // check if we have anything to write 840 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) { 841 // if we've already added a cookie to the return list, append a "; " so 842 // that subsequent cookies are delimited in the final list. 843 if (!aCookieString.IsEmpty()) { 844 aCookieString.AppendLiteral("; "); 845 } 846 847 if (!cookie->Name().IsEmpty()) { 848 // we have a name and value - write both 849 aCookieString += cookie->Name() + "="_ns + cookie->Value(); 850 } else { 851 // just write value 852 aCookieString += cookie->Value(); 853 } 854 } 855 } 856 } 857 858 // static 859 CookieCommons::SecurityChecksResult 860 CookieCommons::CheckGlobalAndRetrieveCookiePrincipals( 861 Document* aDocument, nsIPrincipal** aCookiePrincipal, 862 nsIPrincipal** aCookiePartitionedPrincipal) { 863 MOZ_ASSERT(aCookiePrincipal); 864 865 nsCOMPtr<nsIPrincipal> cookiePrincipal; 866 nsCOMPtr<nsIPrincipal> cookiePartitionedPrincipal; 867 868 if (!NS_IsMainThread()) { 869 MOZ_ASSERT(!aDocument); 870 871 dom::WorkerPrivate* workerPrivate = dom::GetCurrentThreadWorkerPrivate(); 872 MOZ_ASSERT(workerPrivate); 873 874 StorageAccess storageAccess = workerPrivate->StorageAccess(); 875 if (storageAccess == StorageAccess::eDeny) { 876 return SecurityChecksResult::eDoNotContinue; 877 } 878 879 cookiePrincipal = workerPrivate->GetPrincipal(); 880 if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) { 881 return SecurityChecksResult::eSecurityError; 882 } 883 884 // CHIPS - If CHIPS is enabled the partitioned cookie jar is always 885 // available (and therefore the partitioned principal), the unpartitioned 886 // cookie jar is only available in first-party or third-party with 887 // storageAccess contexts. In both cases, the Worker will have storage 888 // access. 889 bool isCHIPS = 890 StaticPrefs::network_cookie_CHIPS_enabled() && 891 !workerPrivate->CookieJarSettings()->GetBlockingAllContexts(); 892 bool workerHasStorageAccess = 893 workerPrivate->StorageAccess() == StorageAccess::eAllow; 894 895 if (isCHIPS && workerHasStorageAccess) { 896 // Only retrieve the partitioned originAttributes if the partitionKey is 897 // set. The partitionKey could be empty for partitionKey in partitioned 898 // originAttributes if the aWorker is for privilege context, such as the 899 // extension's background page. 900 nsCOMPtr<nsIPrincipal> partitionedPrincipal = 901 workerPrivate->GetPartitionedPrincipal(); 902 if (partitionedPrincipal && !partitionedPrincipal->OriginAttributesRef() 903 .mPartitionKey.IsEmpty()) { 904 cookiePartitionedPrincipal = partitionedPrincipal; 905 } 906 } 907 } else { 908 if (!aDocument) { 909 return SecurityChecksResult::eDoNotContinue; 910 } 911 912 // If the document's sandboxed origin flag is set, then reading cookies 913 // is prohibited. 914 if (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN) { 915 return SecurityChecksResult::eSandboxedError; 916 } 917 918 cookiePrincipal = aDocument->EffectiveCookiePrincipal(); 919 if (NS_WARN_IF(!cookiePrincipal) || cookiePrincipal->GetIsNullPrincipal()) { 920 return SecurityChecksResult::eSecurityError; 921 } 922 923 if (aDocument->CookieAccessDisabled()) { 924 return SecurityChecksResult::eDoNotContinue; 925 } 926 927 // GTests do not create an inner window and because of these a few security 928 // checks will block this method. 929 if (!StaticPrefs::dom_cookie_testing_enabled()) { 930 StorageAccess storageAccess = CookieAllowedForDocument(aDocument); 931 if (storageAccess == StorageAccess::eDeny) { 932 return SecurityChecksResult::eDoNotContinue; 933 } 934 935 if (ShouldPartitionStorage(storageAccess) && 936 !StoragePartitioningEnabled(storageAccess, 937 aDocument->CookieJarSettings())) { 938 return SecurityChecksResult::eDoNotContinue; 939 } 940 941 // If the document is a cookie-averse Document... return the empty string. 942 if (aDocument->IsCookieAverse()) { 943 return SecurityChecksResult::eDoNotContinue; 944 } 945 } 946 947 // CHIPS - If CHIPS is enabled the partitioned cookie jar is always 948 // available (and therefore the partitioned principal), the unpartitioned 949 // cookie jar is only available in first-party or third-party with 950 // storageAccess contexts. In both cases, the aDocument will have storage 951 // access. 952 bool isCHIPS = StaticPrefs::network_cookie_CHIPS_enabled() && 953 !aDocument->CookieJarSettings()->GetBlockingAllContexts(); 954 bool documentHasStorageAccess = false; 955 nsresult rv = aDocument->HasStorageAccessSync(documentHasStorageAccess); 956 if (NS_WARN_IF(NS_FAILED(rv))) { 957 return SecurityChecksResult::eDoNotContinue; 958 } 959 960 if (isCHIPS && documentHasStorageAccess) { 961 // Only append the partitioned originAttributes if the partitionKey is 962 // set. The partitionKey could be empty for partitionKey in partitioned 963 // originAttributes if the aDocument is for privilege context, such as the 964 // extension's background page. 965 if (!aDocument->PartitionedPrincipal() 966 ->OriginAttributesRef() 967 .mPartitionKey.IsEmpty()) { 968 cookiePartitionedPrincipal = aDocument->PartitionedPrincipal(); 969 } 970 } 971 } 972 973 if (!IsSchemeSupported(cookiePrincipal)) { 974 return SecurityChecksResult::eDoNotContinue; 975 } 976 977 cookiePrincipal.forget(aCookiePrincipal); 978 979 if (aCookiePartitionedPrincipal) { 980 cookiePartitionedPrincipal.forget(aCookiePartitionedPrincipal); 981 } 982 983 return SecurityChecksResult::eContinue; 984 } 985 // static 986 void CookieCommons::GetServerDateHeader(nsIChannel* aChannel, 987 nsACString& aServerDateHeader) { 988 if (!aChannel) { 989 return; 990 } 991 992 nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aChannel); 993 if (NS_WARN_IF(!channel)) { 994 return; 995 } 996 997 (void)channel->GetResponseHeader("Date"_ns, aServerDateHeader); 998 } 999 1000 // static 1001 int64_t CookieCommons::MaybeCapExpiry(int64_t aCurrentTimeInMSec, 1002 int64_t aExpiryInMSec) { 1003 const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); 1004 1005 if (maxageCap) { 1006 aExpiryInMSec = 1007 std::min(aExpiryInMSec, aCurrentTimeInMSec + maxageCap * 1000); 1008 } 1009 1010 return aExpiryInMSec; 1011 } 1012 1013 int64_t CookieCommons::MaybeCapMaxAge(int64_t aCurrentTimeInMSec, 1014 int64_t aMaxAgeInSec) { 1015 const int64_t maxageCap = StaticPrefs::network_cookie_maxageCap(); 1016 CheckedInt<int64_t> value(aCurrentTimeInMSec); 1017 1018 value += 1019 (maxageCap ? std::min(aMaxAgeInSec, maxageCap) : aMaxAgeInSec) * 1000; 1020 return value.isValid() ? value.value() : INT64_MAX; 1021 } 1022 1023 // static 1024 bool CookieCommons::IsSubdomainOf(const nsACString& a, const nsACString& b) { 1025 if (a == b) { 1026 return true; 1027 } 1028 if (a.Length() > b.Length()) { 1029 return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b); 1030 } 1031 return false; 1032 } 1033 1034 // static 1035 int64_t CookieCommons::GetCurrentTimeInUSecFromChannel(nsIChannel* aChannel) { 1036 nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(aChannel); 1037 if (timedChannel) { 1038 PRTime currentTimeInUSec = 0; 1039 nsresult rv = timedChannel->GetResponseStartTime(¤tTimeInUSec); 1040 if (NS_SUCCEEDED(rv) && currentTimeInUSec) { 1041 return currentTimeInUSec; 1042 } 1043 } 1044 1045 return PR_Now(); 1046 } 1047 1048 } // namespace net 1049 } // namespace mozilla