ThirdPartyUtil.cpp (17628B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ThirdPartyUtil.h" 8 9 #include <cstdint> 10 11 #include "MainThreadUtils.h" 12 #include "mozIDOMWindow.h" 13 #include "mozilla/Assertions.h" 14 #include "mozilla/BasePrincipal.h" 15 #include "mozilla/ClearOnShutdown.h" 16 #include "mozilla/Components.h" 17 #include "mozilla/ContentBlockingNotifier.h" 18 #include "mozilla/Logging.h" 19 #include "mozilla/NullPrincipal.h" 20 #include "mozilla/StaticPtr.h" 21 #include "mozilla/StorageAccess.h" 22 #include "mozilla/dom/BlobURLProtocolHandler.h" 23 #include "mozilla/dom/BrowsingContext.h" 24 #include "mozilla/dom/CanonicalBrowsingContext.h" 25 #include "mozilla/dom/Document.h" 26 #include "mozilla/dom/WindowContext.h" 27 #include "mozilla/dom/WindowGlobalParent.h" 28 #include "nsCOMPtr.h" 29 #include "nsDebug.h" 30 #include "nsError.h" 31 #include "nsGlobalWindowOuter.h" 32 #include "nsIChannel.h" 33 #include "nsIClassifiedChannel.h" 34 #include "nsIContentPolicy.h" 35 #include "nsIHttpChannelInternal.h" 36 #include "nsILoadContext.h" 37 #include "nsILoadInfo.h" 38 #include "nsIPrincipal.h" 39 #include "nsIScriptObjectPrincipal.h" 40 #include "nsIURI.h" 41 #include "nsLiteralString.h" 42 #include "nsNetUtil.h" 43 #include "nsPIDOMWindow.h" 44 #include "nsPIDOMWindowInlines.h" 45 #include "nsSandboxFlags.h" 46 #include "nsServiceManagerUtils.h" 47 #include "nsTLiteralString.h" 48 49 using namespace mozilla; 50 using namespace mozilla::dom; 51 52 NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil) 53 54 // 55 // MOZ_LOG=thirdPartyUtil:5 56 // 57 static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil"); 58 #undef LOG 59 #define LOG(args) MOZ_LOG(gThirdPartyLog, mozilla::LogLevel::Debug, args) 60 61 static mozilla::StaticRefPtr<ThirdPartyUtil> gService; 62 63 // static 64 void ThirdPartyUtil::Startup() { 65 nsCOMPtr<mozIThirdPartyUtil> tpu; 66 if (NS_WARN_IF(!(tpu = do_GetService(THIRDPARTYUTIL_CONTRACTID)))) { 67 NS_WARNING("Failed to get third party util!"); 68 } 69 } 70 71 nsresult ThirdPartyUtil::Init() { 72 NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_NOT_AVAILABLE); 73 74 MOZ_ASSERT(!gService); 75 gService = this; 76 mozilla::ClearOnShutdown(&gService); 77 78 mTLDService = mozilla::components::EffectiveTLD::Service(); 79 return mTLDService ? NS_OK : NS_ERROR_FAILURE; 80 } 81 82 ThirdPartyUtil::~ThirdPartyUtil() { gService = nullptr; } 83 84 // static 85 ThirdPartyUtil* ThirdPartyUtil::GetInstance() { 86 if (gService) { 87 return gService; 88 } 89 nsCOMPtr<mozIThirdPartyUtil> tpuService = 90 mozilla::components::ThirdPartyUtil::Service(); 91 if (!tpuService) { 92 return nullptr; 93 } 94 MOZ_ASSERT( 95 gService, 96 "gService must have been initialized in nsEffectiveTLDService::Init"); 97 return gService; 98 } 99 100 // Determine if aFirstDomain is a different base domain to aSecondURI; or, if 101 // the concept of base domain does not apply, determine if the two hosts are not 102 // string-identical. 103 nsresult ThirdPartyUtil::IsThirdPartyInternal(const nsCString& aFirstDomain, 104 nsIURI* aSecondURI, 105 bool* aResult) { 106 if (!aSecondURI) { 107 return NS_ERROR_INVALID_ARG; 108 } 109 110 // BlobURLs are always first-party. 111 if (aSecondURI->SchemeIs("blob")) { 112 *aResult = false; 113 return NS_OK; 114 } 115 116 // Get the base domain for aSecondURI. 117 nsAutoCString secondDomain; 118 nsresult rv = GetBaseDomain(aSecondURI, secondDomain); 119 LOG(("ThirdPartyUtil::IsThirdPartyInternal %s =? %s", aFirstDomain.get(), 120 secondDomain.get())); 121 if (NS_FAILED(rv)) { 122 return rv; 123 } 124 125 *aResult = IsThirdPartyInternal(aFirstDomain, secondDomain); 126 return NS_OK; 127 } 128 129 NS_IMETHODIMP 130 ThirdPartyUtil::GetPrincipalFromWindow(mozIDOMWindowProxy* aWin, 131 nsIPrincipal** result) { 132 nsCOMPtr<nsIScriptObjectPrincipal> scriptObjPrin = do_QueryInterface(aWin); 133 if (!scriptObjPrin) { 134 return NS_ERROR_INVALID_ARG; 135 } 136 137 nsCOMPtr<nsIPrincipal> prin = scriptObjPrin->GetPrincipal(); 138 if (!prin) { 139 return NS_ERROR_INVALID_ARG; 140 } 141 142 prin.forget(result); 143 return NS_OK; 144 } 145 146 // Get the URI associated with a window. 147 NS_IMETHODIMP 148 ThirdPartyUtil::GetURIFromWindow(mozIDOMWindowProxy* aWin, nsIURI** result) { 149 nsCOMPtr<nsIPrincipal> prin; 150 nsresult rv = GetPrincipalFromWindow(aWin, getter_AddRefs(prin)); 151 if (NS_FAILED(rv)) { 152 return rv; 153 } 154 155 if (prin->GetIsNullPrincipal()) { 156 LOG(("ThirdPartyUtil::GetURIFromWindow can't use null principal\n")); 157 return NS_ERROR_INVALID_ARG; 158 } 159 auto* basePrin = BasePrincipal::Cast(prin); 160 return basePrin->GetURI(result); 161 } 162 163 // Determine if aFirstURI is third party with respect to aSecondURI. See docs 164 // for mozIThirdPartyUtil. 165 NS_IMETHODIMP 166 ThirdPartyUtil::IsThirdPartyURI(nsIURI* aFirstURI, nsIURI* aSecondURI, 167 bool* aResult) { 168 NS_ENSURE_ARG(aFirstURI); 169 NS_ENSURE_ARG(aSecondURI); 170 NS_ASSERTION(aResult, "null outparam pointer"); 171 172 nsAutoCString firstHost; 173 nsresult rv = GetBaseDomain(aFirstURI, firstHost); 174 if (NS_FAILED(rv)) { 175 return rv; 176 } 177 178 return IsThirdPartyInternal(firstHost, aSecondURI, aResult); 179 } 180 181 // If the optional aURI is provided, determine whether aWindow is foreign with 182 // respect to aURI. If the optional aURI is not provided, determine whether the 183 // given "window hierarchy" is third party. See docs for mozIThirdPartyUtil. 184 NS_IMETHODIMP 185 ThirdPartyUtil::IsThirdPartyWindow(mozIDOMWindowProxy* aWindow, nsIURI* aURI, 186 bool* aResult) { 187 NS_ENSURE_ARG(aWindow); 188 NS_ASSERTION(aResult, "null outparam pointer"); 189 190 bool result; 191 192 // Ignore about:blank and about:srcdoc URIs here since they have no domain 193 // and attempting to compare against them will fail. 194 if (aURI && !NS_IsAboutBlank(aURI) && !NS_IsAboutSrcdoc(aURI)) { 195 nsCOMPtr<nsIPrincipal> prin; 196 nsresult rv = GetPrincipalFromWindow(aWindow, getter_AddRefs(prin)); 197 NS_ENSURE_SUCCESS(rv, rv); 198 // Determine whether aURI is foreign with respect to the current principal. 199 rv = prin->IsThirdPartyURI(aURI, &result); 200 if (NS_FAILED(rv)) { 201 return rv; 202 } 203 204 if (result) { 205 *aResult = true; 206 return NS_OK; 207 } 208 } 209 210 nsPIDOMWindowOuter* current = nsPIDOMWindowOuter::From(aWindow); 211 auto* const browsingContext = current->GetBrowsingContext(); 212 MOZ_ASSERT(browsingContext); 213 214 if (browsingContext->IsTopContent()) { 215 *aResult = false; 216 return NS_OK; 217 } 218 219 // If the document is sandboxed, it's always third party. 220 RefPtr<Document> doc = current->GetExtantDoc(); 221 if (doc && (doc->GetSandboxFlags() & SANDBOXED_ORIGIN)) { 222 *aResult = true; 223 return NS_OK; 224 } 225 226 WindowContext* wc = browsingContext->GetCurrentWindowContext(); 227 if (NS_WARN_IF(!wc)) { 228 *aResult = true; 229 return NS_OK; 230 } 231 232 *aResult = wc->GetIsThirdPartyWindow(); 233 return NS_OK; 234 } 235 236 nsresult ThirdPartyUtil::IsThirdPartyGlobal( 237 mozilla::dom::WindowGlobalParent* aWindowGlobal, bool* aResult) { 238 NS_ENSURE_ARG(aWindowGlobal); 239 NS_ASSERTION(aResult, "null outparam pointer"); 240 241 auto* currentWGP = aWindowGlobal; 242 do { 243 MOZ_ASSERT(currentWGP->BrowsingContext()); 244 if (currentWGP->BrowsingContext()->IsTop()) { 245 *aResult = false; 246 return NS_OK; 247 } 248 249 // If the window global is sandboxed, it's always third party. 250 if (currentWGP->SandboxFlags() & SANDBOXED_ORIGIN) { 251 *aResult = true; 252 return NS_OK; 253 } 254 255 nsCOMPtr<nsIPrincipal> currentPrincipal = currentWGP->DocumentPrincipal(); 256 RefPtr<WindowGlobalParent> parent = 257 currentWGP->BrowsingContext()->GetEmbedderWindowGlobal(); 258 if (!parent) { 259 return NS_ERROR_FAILURE; 260 } 261 nsCOMPtr<nsIPrincipal> parentPrincipal = parent->DocumentPrincipal(); 262 nsresult rv = 263 currentPrincipal->IsThirdPartyPrincipal(parentPrincipal, aResult); 264 if (NS_FAILED(rv)) { 265 return rv; 266 } 267 268 if (*aResult) { 269 return NS_OK; 270 } 271 272 currentWGP = parent; 273 } while (true); 274 } 275 276 // Determine if the URI associated with aChannel or any URI of the window 277 // hierarchy associated with the channel is foreign with respect to aSecondURI. 278 // See docs for mozIThirdPartyUtil. 279 NS_IMETHODIMP 280 ThirdPartyUtil::IsThirdPartyChannel(nsIChannel* aChannel, nsIURI* aURI, 281 bool* aResult) { 282 LOG(("ThirdPartyUtil::IsThirdPartyChannel [channel=%p]", aChannel)); 283 NS_ENSURE_ARG(aChannel); 284 NS_ASSERTION(aResult, "null outparam pointer"); 285 286 nsresult rv; 287 bool doForce = false; 288 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = 289 do_QueryInterface(aChannel); 290 if (httpChannelInternal) { 291 uint32_t flags = 0; 292 // Avoid checking the return value here since some channel implementations 293 // may return NS_ERROR_NOT_IMPLEMENTED. 294 (void)httpChannelInternal->GetThirdPartyFlags(&flags); 295 296 doForce = (flags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); 297 298 // If aURI was not supplied, and we're forcing, then we're by definition 299 // not foreign. If aURI was supplied, we still want to check whether it's 300 // foreign with respect to the channel URI. (The forcing only applies to 301 // whatever window hierarchy exists above the channel.) 302 if (doForce && !aURI) { 303 *aResult = false; 304 return NS_OK; 305 } 306 } 307 308 bool parentIsThird = false; 309 nsAutoCString channelDomain; 310 311 // Obtain the URI from the channel, and its base domain. 312 nsCOMPtr<nsIURI> channelURI; 313 rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); 314 if (NS_FAILED(rv)) { 315 return rv; 316 } 317 318 BasePrincipal* loadingPrincipal = nullptr; 319 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 320 321 if (!doForce) { 322 parentIsThird = loadInfo->GetIsInThirdPartyContext(); 323 if (!parentIsThird && loadInfo->GetExternalContentPolicyType() != 324 ExtContentPolicy::TYPE_DOCUMENT) { 325 // Check if the channel itself is third-party to its own requestor. 326 // Unfortunately, we have to go through the loading principal. 327 loadingPrincipal = BasePrincipal::Cast(loadInfo->GetLoadingPrincipal()); 328 } 329 } 330 331 // Special consideration must be done for about:blank and about:srcdoc URIs 332 // because those inherit the principal from the parent context. For them, 333 // let's consider the principal URI. 334 if (NS_IsAboutBlank(channelURI) || NS_IsAboutSrcdoc(channelURI)) { 335 nsCOMPtr<nsIPrincipal> principalToInherit = 336 loadInfo->FindPrincipalToInherit(aChannel); 337 if (!principalToInherit) { 338 *aResult = true; 339 return NS_OK; 340 } 341 342 rv = principalToInherit->GetBaseDomain(channelDomain); 343 if (NS_FAILED(rv)) { 344 return rv; 345 } 346 347 if (loadingPrincipal) { 348 rv = loadingPrincipal->IsThirdPartyPrincipal(principalToInherit, 349 &parentIsThird); 350 if (NS_FAILED(rv)) { 351 return rv; 352 } 353 } 354 } else { 355 rv = GetBaseDomain(channelURI, channelDomain); 356 if (NS_FAILED(rv)) { 357 return rv; 358 } 359 360 if (loadingPrincipal) { 361 rv = loadingPrincipal->IsThirdPartyURI(channelURI, &parentIsThird); 362 if (NS_FAILED(rv)) { 363 return rv; 364 } 365 } 366 } 367 368 // If we're not comparing to a URI, we have our answer. Otherwise, if 369 // parentIsThird, we're not forcing and we know that we're a third-party 370 // request. 371 if (!aURI || parentIsThird) { 372 *aResult = parentIsThird; 373 return NS_OK; 374 } 375 376 // Determine whether aURI is foreign with respect to channelURI. 377 return IsThirdPartyInternal(channelDomain, aURI, aResult); 378 } 379 380 NS_IMETHODIMP 381 ThirdPartyUtil::GetTopWindowForChannel(nsIChannel* aChannel, 382 nsIURI* aURIBeingLoaded, 383 mozIDOMWindowProxy** aWin) { 384 NS_ENSURE_ARG(aWin); 385 386 // Find the associated window and its parent window. 387 nsCOMPtr<nsILoadContext> ctx; 388 NS_QueryNotificationCallbacks(aChannel, ctx); 389 if (!ctx) { 390 return NS_ERROR_INVALID_ARG; 391 } 392 393 nsCOMPtr<mozIDOMWindowProxy> window; 394 ctx->GetAssociatedWindow(getter_AddRefs(window)); 395 if (!window) { 396 return NS_ERROR_INVALID_ARG; 397 } 398 399 nsCOMPtr<nsPIDOMWindowOuter> top = 400 nsGlobalWindowOuter::Cast(window) 401 ->GetTopExcludingExtensionAccessibleContentFrames(aURIBeingLoaded); 402 top.forget(aWin); 403 return NS_OK; 404 } 405 406 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be 407 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing 408 // dot may be present. If aHostURI is an IP address, an alias such as 409 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will 410 // be the exact host. Blob URIs will incur a lookup for their blob URL entry, 411 // and will perform the same construction from their principal's base domain. 412 // The result of this function should only be used in exact 413 // string comparisons, since substring comparisons will not be valid for the 414 // special cases elided above. 415 NS_IMETHODIMP 416 ThirdPartyUtil::GetBaseDomain(nsIURI* aHostURI, nsACString& aBaseDomain) { 417 if (!aHostURI) { 418 return NS_ERROR_INVALID_ARG; 419 } 420 421 // First, get the base domain from aHostURI. In the common case, this is 422 // direct. For blob URLs we get this from the blob url's entry in the blob url 423 // store. 424 nsresult rv; 425 nsCOMPtr<nsIPrincipal> blobPrincipal; 426 if (aHostURI->SchemeIs("blob")) { 427 if (BlobURLProtocolHandler::GetBlobURLPrincipal( 428 aHostURI, getter_AddRefs(blobPrincipal))) { 429 // If the blob URL is expired, this will be the uuid of a NullPrincipal 430 rv = blobPrincipal->GetBaseDomain(aBaseDomain); 431 } else { 432 // If the blob is expired and no longer has a map entry, we fail 433 rv = nsresult::NS_ERROR_DOM_BAD_URI; 434 } 435 } else { 436 rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); 437 } 438 439 // If the URI is a null principal, we get the UUID portion instead of the base 440 // domain because a null principal uri doesn't have a base domain. 441 if (aHostURI->SchemeIs(NS_NULLPRINCIPAL_SCHEME)) { 442 rv = aHostURI->GetFilePath(aBaseDomain); 443 } 444 445 // Get the base domain. this will fail if the host contains a leading dot, 446 // more than one trailing dot, or is otherwise malformed. 447 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || 448 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { 449 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD 450 // such as 'co.uk', or the empty string. Uses the normalized host in such 451 // cases. 452 453 // The aHostURI can be a view-source URI, in which case we need to get the 454 // base domain from the inner most URI. 455 if (aHostURI->SchemeIs("view-source")) { 456 rv = NS_GetInnermostURIHost(aHostURI, aBaseDomain); 457 } else { 458 rv = aHostURI->GetAsciiHost(aBaseDomain); 459 } 460 } 461 462 if (NS_FAILED(rv)) { 463 return rv; 464 } 465 466 // aHostURI (and thus aBaseDomain) may be the string '.'. If so, fail. 467 if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') { 468 return NS_ERROR_INVALID_ARG; 469 } 470 471 // Reject any URIs without a host that aren't file:// URIs. This makes it the 472 // only way we can get a base domain consisting of the empty string, which 473 // means we can safely perform foreign tests on such URIs where "not foreign" 474 // means "the involved URIs are all file://". 475 if (aBaseDomain.IsEmpty() && !aHostURI->SchemeIs("file")) { 476 return NS_ERROR_INVALID_ARG; 477 } 478 479 return NS_OK; 480 } 481 482 NS_IMETHODIMP_(ThirdPartyAnalysisResult) 483 ThirdPartyUtil::AnalyzeChannel(nsIChannel* aChannel, bool aNotify, nsIURI* aURI, 484 RequireThirdPartyCheck aRequireThirdPartyCheck, 485 uint32_t* aRejectedReason) { 486 MOZ_ASSERT_IF(aNotify, aRejectedReason); 487 488 ThirdPartyAnalysisResult result; 489 490 nsCOMPtr<nsIURI> uri; 491 if (!aURI && aChannel) { 492 aChannel->GetURI(getter_AddRefs(uri)); 493 } 494 nsCOMPtr<nsILoadInfo> loadInfo = aChannel ? aChannel->LoadInfo() : nullptr; 495 496 bool isForeign = true; 497 if (aChannel && 498 (!aRequireThirdPartyCheck || aRequireThirdPartyCheck(loadInfo))) { 499 IsThirdPartyChannel(aChannel, aURI ? aURI : uri.get(), &isForeign); 500 } 501 if (isForeign) { 502 result += ThirdPartyAnalysis::IsForeign; 503 } 504 505 nsCOMPtr<nsIClassifiedChannel> classifiedChannel = 506 do_QueryInterface(aChannel); 507 if (classifiedChannel) { 508 if (classifiedChannel->IsThirdPartyTrackingResource()) { 509 result += ThirdPartyAnalysis::IsThirdPartyTrackingResource; 510 } 511 if (classifiedChannel->IsThirdPartySocialTrackingResource()) { 512 result += ThirdPartyAnalysis::IsThirdPartySocialTrackingResource; 513 } 514 515 // Check first-party storage access even for non-tracking resources, since 516 // we will need the result when computing the access rights for the reject 517 // foreign cookie behavior mode. 518 519 // If the caller has requested third-party checks, we will only perform the 520 // storage access check once we know we're in the third-party context. 521 bool performStorageChecks = 522 aRequireThirdPartyCheck ? result.contains(ThirdPartyAnalysis::IsForeign) 523 : true; 524 if (performStorageChecks && 525 ShouldAllowAccessFor(aChannel, aURI ? aURI : uri.get(), 526 aRejectedReason)) { 527 result += ThirdPartyAnalysis::IsStorageAccessPermissionGranted; 528 } 529 530 if (aNotify && !result.contains( 531 ThirdPartyAnalysis::IsStorageAccessPermissionGranted)) { 532 ContentBlockingNotifier::OnDecision( 533 aChannel, ContentBlockingNotifier::BlockingDecision::eBlock, 534 *aRejectedReason); 535 } 536 } 537 538 return result; 539 }