WorkerLoadInfo.cpp (16809B)
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 "WorkerLoadInfo.h" 8 9 #include "WorkerPrivate.h" 10 #include "mozilla/BasePrincipal.h" 11 #include "mozilla/LoadContext.h" 12 #include "mozilla/StorageAccess.h" 13 #include "mozilla/StoragePrincipalHelper.h" 14 #include "mozilla/dom/BrowserChild.h" 15 #include "mozilla/dom/PolicyContainer.h" 16 #include "mozilla/dom/ReferrerInfo.h" 17 #include "mozilla/dom/nsCSPUtils.h" 18 #include "mozilla/ipc/BackgroundUtils.h" 19 #include "mozilla/ipc/PBackgroundSharedTypes.h" 20 #include "nsContentUtils.h" 21 #include "nsIBrowserChild.h" 22 #include "nsIContentSecurityPolicy.h" 23 #include "nsICookieJarSettings.h" 24 #include "nsINetworkInterceptController.h" 25 #include "nsIProtocolHandler.h" 26 #include "nsIReferrerInfo.h" 27 #include "nsNetUtil.h" 28 #include "nsScriptSecurityManager.h" 29 30 namespace mozilla { 31 32 using namespace ipc; 33 34 namespace dom { 35 36 namespace { 37 38 class MainThreadReleaseRunnable final : public Runnable { 39 nsTArray<nsCOMPtr<nsISupports>> mDoomed; 40 nsCOMPtr<nsILoadGroup> mLoadGroupToCancel; 41 42 public: 43 MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed, 44 nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel) 45 : mozilla::Runnable("MainThreadReleaseRunnable"), 46 mDoomed(std::move(aDoomed)), 47 mLoadGroupToCancel(std::move(aLoadGroupToCancel)) {} 48 49 NS_INLINE_DECL_REFCOUNTING_INHERITED(MainThreadReleaseRunnable, Runnable) 50 51 NS_IMETHOD 52 Run() override { 53 if (mLoadGroupToCancel) { 54 mLoadGroupToCancel->CancelWithReason( 55 NS_BINDING_ABORTED, "WorkerLoadInfo::MainThreadReleaseRunnable"_ns); 56 mLoadGroupToCancel = nullptr; 57 } 58 59 mDoomed.Clear(); 60 return NS_OK; 61 } 62 63 private: 64 ~MainThreadReleaseRunnable() = default; 65 }; 66 67 // Specialize this if there's some class that has multiple nsISupports bases. 68 template <class T> 69 struct ISupportsBaseInfo { 70 using ISupportsBase = T; 71 }; 72 73 template <template <class> class SmartPtr, class T> 74 inline void SwapToISupportsArray(SmartPtr<T>& aSrc, 75 nsTArray<nsCOMPtr<nsISupports>>& aDest) { 76 nsCOMPtr<nsISupports>* dest = aDest.AppendElement(); 77 78 T* raw = nullptr; 79 aSrc.swap(raw); 80 81 nsISupports* rawSupports = 82 static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw); 83 dest->swap(rawSupports); 84 } 85 86 } // namespace 87 88 WorkerLoadInfoData::WorkerLoadInfoData() 89 : mLoadFlags(nsIRequest::LOAD_NORMAL), 90 mWindowID(UINT64_MAX), 91 mAssociatedBrowsingContextID(0), 92 mReferrerInfo(new ReferrerInfo(nullptr)), 93 mFromWindow(false), 94 mXHRParamsAllowed(false), 95 mWatchedByDevTools(false), 96 mStorageAccess(StorageAccess::eDeny), 97 mUseRegularPrincipal(false), 98 mUsingStorageAccess(false), 99 mServiceWorkersTestingInWindow(false), 100 mShouldResistFingerprinting(false), 101 mIsThirdPartyContext(true), 102 mSecureContext(eNotSet) {} 103 104 nsresult WorkerLoadInfo::SetPrincipalsAndCSPOnMainThread( 105 nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, 106 nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) { 107 AssertIsOnMainThread(); 108 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal)); 109 110 mPrincipal = aPrincipal; 111 mPartitionedPrincipal = aPartitionedPrincipal; 112 113 mCSP = aCsp; 114 115 if (mCSP) { 116 Result<UniquePtr<WorkerCSPContext>, nsresult> ctx = 117 WorkerCSPContext::CreateFromCSP(aCsp); 118 if (NS_WARN_IF(ctx.isErr())) { 119 return ctx.unwrapErr(); 120 } 121 mCSPContext = ctx.unwrap(); 122 } 123 124 mLoadGroup = aLoadGroup; 125 126 mPrincipalInfo = MakeUnique<PrincipalInfo>(); 127 mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>(); 128 StoragePrincipalHelper::GetRegularPrincipalOriginAttributes( 129 aLoadGroup, mOriginAttributes); 130 131 nsresult rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo.get()); 132 NS_ENSURE_SUCCESS(rv, rv); 133 134 if (aPrincipal->Equals(aPartitionedPrincipal)) { 135 *mPartitionedPrincipalInfo = *mPrincipalInfo; 136 } else { 137 mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>(); 138 rv = PrincipalToPrincipalInfo(aPartitionedPrincipal, 139 mPartitionedPrincipalInfo.get()); 140 NS_ENSURE_SUCCESS(rv, rv); 141 } 142 return NS_OK; 143 } 144 145 nsresult WorkerLoadInfo::GetPrincipalsAndLoadGroupFromChannel( 146 nsIChannel* aChannel, nsIPrincipal** aPrincipalOut, 147 nsIPrincipal** aPartitionedPrincipalOut, nsILoadGroup** aLoadGroupOut) { 148 AssertIsOnMainThread(); 149 MOZ_DIAGNOSTIC_ASSERT(aChannel); 150 MOZ_DIAGNOSTIC_ASSERT(aPrincipalOut); 151 MOZ_DIAGNOSTIC_ASSERT(aPartitionedPrincipalOut); 152 MOZ_DIAGNOSTIC_ASSERT(aLoadGroupOut); 153 154 // Initial triggering principal should be set 155 NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_DOM_INVALID_STATE_ERR); 156 157 nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); 158 MOZ_DIAGNOSTIC_ASSERT(ssm); 159 160 nsCOMPtr<nsIPrincipal> channelPrincipal; 161 nsCOMPtr<nsIPrincipal> channelPartitionedPrincipal; 162 nsresult rv = ssm->GetChannelResultPrincipals( 163 aChannel, getter_AddRefs(channelPrincipal), 164 getter_AddRefs(channelPartitionedPrincipal)); 165 NS_ENSURE_SUCCESS(rv, rv); 166 167 // Every time we call GetChannelResultPrincipal() it will return a different 168 // null principal for a data URL. We don't want to change the worker's 169 // principal again, though. Instead just keep the original null principal we 170 // first got from the channel. 171 // 172 // Note, we don't do this by setting principalToInherit on the channel's 173 // load info because we don't yet have the first null principal when we 174 // create the channel. 175 if (mPrincipal && mPrincipal->GetIsNullPrincipal() && 176 channelPrincipal->GetIsNullPrincipal()) { 177 channelPrincipal = mPrincipal; 178 channelPartitionedPrincipal = mPrincipal; 179 } 180 181 nsCOMPtr<nsILoadGroup> channelLoadGroup; 182 rv = aChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); 183 NS_ENSURE_SUCCESS(rv, rv); 184 MOZ_ASSERT(channelLoadGroup); 185 186 // If the loading principal is the system principal then the channel 187 // principal must also be the system principal (we do not allow chrome 188 // code to create workers with non-chrome scripts, and if we ever decide 189 // to change this we need to make sure we don't always set 190 // mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise 191 // this channel principal must be same origin with the load principal (we 192 // check again here in case redirects changed the location of the script). 193 if (mLoadingPrincipal->IsSystemPrincipal()) { 194 if (!channelPrincipal->IsSystemPrincipal()) { 195 nsCOMPtr<nsIURI> finalURI; 196 rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI)); 197 NS_ENSURE_SUCCESS(rv, rv); 198 199 // See if this is a resource URI. Since JSMs usually come from 200 // resource:// URIs we're currently considering all URIs with the 201 // URI_IS_UI_RESOURCE flag as valid for creating privileged workers. 202 bool isResource; 203 rv = NS_URIChainHasFlags(finalURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, 204 &isResource); 205 NS_ENSURE_SUCCESS(rv, rv); 206 207 if (isResource) { 208 // Assign the system principal to the resource:// worker only if it 209 // was loaded from code using the system principal. 210 channelPrincipal = mLoadingPrincipal; 211 channelPartitionedPrincipal = mLoadingPrincipal; 212 } else { 213 return NS_ERROR_DOM_BAD_URI; 214 } 215 } 216 } 217 218 // The principal can change, but it should still match the original 219 // load group's browser element flag. 220 MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal)); 221 222 channelPrincipal.forget(aPrincipalOut); 223 channelPartitionedPrincipal.forget(aPartitionedPrincipalOut); 224 channelLoadGroup.forget(aLoadGroupOut); 225 226 return NS_OK; 227 } 228 229 nsresult WorkerLoadInfo::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) { 230 AssertIsOnMainThread(); 231 232 nsCOMPtr<nsIPrincipal> principal; 233 nsCOMPtr<nsIPrincipal> partitionedPrincipal; 234 nsCOMPtr<nsILoadGroup> loadGroup; 235 nsresult rv = GetPrincipalsAndLoadGroupFromChannel( 236 aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal), 237 getter_AddRefs(loadGroup)); 238 NS_ENSURE_SUCCESS(rv, rv); 239 240 // Workers themselves can have their own CSP - Workers of an opaque origin 241 // however inherit the CSP of the document that spawned the worker. 242 nsCOMPtr<nsIContentSecurityPolicy> csp; 243 if (CSP_ShouldResponseInheritCSP(aChannel)) { 244 nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo(); 245 nsCOMPtr<nsIPolicyContainer> policyContainer = 246 loadinfo->GetPolicyContainer(); 247 csp = PolicyContainer::GetCSP(policyContainer); 248 } 249 return SetPrincipalsAndCSPOnMainThread(principal, partitionedPrincipal, 250 loadGroup, csp); 251 } 252 253 bool WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel) { 254 AssertIsOnMainThread(); 255 256 nsCOMPtr<nsIPrincipal> principal; 257 nsCOMPtr<nsIPrincipal> partitionedPrincipal; 258 nsCOMPtr<nsILoadGroup> loadGroup; 259 nsresult rv = GetPrincipalsAndLoadGroupFromChannel( 260 aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal), 261 getter_AddRefs(loadGroup)); 262 NS_ENSURE_SUCCESS(rv, false); 263 264 // Verify that the channel is still a null principal. We don't care 265 // if these are the exact same null principal object, though. From 266 // the worker's perspective its the same effect. 267 if (principal->GetIsNullPrincipal() && mPrincipal->GetIsNullPrincipal()) { 268 return true; 269 } 270 271 // Otherwise we require exact equality. Redirects can happen, but they 272 // are not allowed to change our principal. 273 if (principal->Equals(mPrincipal)) { 274 return true; 275 } 276 277 return false; 278 } 279 280 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED 281 bool WorkerLoadInfo::PrincipalIsValid() const { 282 return mPrincipal && mPrincipalInfo && 283 mPrincipalInfo->type() != PrincipalInfo::T__None && 284 mPrincipalInfo->type() <= PrincipalInfo::T__Last && 285 mPartitionedPrincipal && mPartitionedPrincipalInfo && 286 mPartitionedPrincipalInfo->type() != PrincipalInfo::T__None && 287 mPartitionedPrincipalInfo->type() <= PrincipalInfo::T__Last; 288 } 289 290 bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() { 291 AssertIsOnMainThread(); 292 293 nsAutoCString scheme; 294 nsresult rv = mBaseURI->GetScheme(scheme); 295 NS_ENSURE_SUCCESS(rv, false); 296 297 // A system principal must either be a blob URL or a resource JSM. 298 if (mPrincipal->IsSystemPrincipal()) { 299 if (scheme == "blob"_ns) { 300 return true; 301 } 302 303 bool isResource = false; 304 nsresult rv = NS_URIChainHasFlags( 305 mBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource); 306 NS_ENSURE_SUCCESS(rv, false); 307 308 return isResource; 309 } 310 311 // A null principal can occur for a data URL worker script or a blob URL 312 // worker script from a sandboxed iframe. 313 if (mPrincipal->GetIsNullPrincipal()) { 314 return scheme == "data"_ns || scheme == "blob"_ns; 315 } 316 317 // The principal for a blob: URL worker script does not have a matching URL. 318 // This is likely a bug in our referer setting logic, but exempt it for now. 319 // This is another reason we should fix bug 1340694 so that referer does not 320 // depend on the principal URI. 321 if (scheme == "blob"_ns) { 322 return true; 323 } 324 325 if (mPrincipal->IsSameOrigin(mBaseURI)) { 326 return true; 327 } 328 329 return false; 330 } 331 #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED 332 333 bool WorkerLoadInfo::ProxyReleaseMainThreadObjects( 334 WorkerPrivate* aWorkerPrivate) { 335 nsCOMPtr<nsILoadGroup> nullLoadGroup; 336 return ProxyReleaseMainThreadObjects(aWorkerPrivate, 337 std::move(nullLoadGroup)); 338 } 339 340 bool WorkerLoadInfo::ProxyReleaseMainThreadObjects( 341 WorkerPrivate* aWorkerPrivate, 342 nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel) { 343 static const uint32_t kDoomedCount = 11; 344 nsTArray<nsCOMPtr<nsISupports>> doomed(kDoomedCount); 345 346 SwapToISupportsArray(mWindow, doomed); 347 SwapToISupportsArray(mScriptContext, doomed); 348 SwapToISupportsArray(mBaseURI, doomed); 349 SwapToISupportsArray(mResolvedScriptURI, doomed); 350 SwapToISupportsArray(mPrincipal, doomed); 351 SwapToISupportsArray(mPartitionedPrincipal, doomed); 352 SwapToISupportsArray(mLoadingPrincipal, doomed); 353 SwapToISupportsArray(mChannel, doomed); 354 SwapToISupportsArray(mCSP, doomed); 355 SwapToISupportsArray(mLoadGroup, doomed); 356 SwapToISupportsArray(mInterfaceRequestor, doomed); 357 // Before adding anything here update kDoomedCount above! 358 359 MOZ_ASSERT(doomed.Length() == kDoomedCount); 360 361 RefPtr<MainThreadReleaseRunnable> runnable = new MainThreadReleaseRunnable( 362 std::move(doomed), std::move(aLoadGroupToCancel)); 363 return NS_SUCCEEDED(aWorkerPrivate->DispatchToMainThread(runnable.forget())); 364 } 365 366 WorkerLoadInfo::InterfaceRequestor::InterfaceRequestor( 367 nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) { 368 MOZ_ASSERT(NS_IsMainThread()); 369 MOZ_ASSERT(aPrincipal); 370 371 // Look for an existing LoadContext. This is optional and it's ok if 372 // we don't find one. 373 nsCOMPtr<nsILoadContext> baseContext; 374 if (aLoadGroup) { 375 nsCOMPtr<nsIInterfaceRequestor> callbacks; 376 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 377 if (callbacks) { 378 callbacks->GetInterface(NS_GET_IID(nsILoadContext), 379 getter_AddRefs(baseContext)); 380 } 381 mOuterRequestor = callbacks; 382 } 383 384 mLoadContext = new LoadContext(aPrincipal, baseContext); 385 } 386 387 void WorkerLoadInfo::InterfaceRequestor::MaybeAddBrowserChild( 388 nsILoadGroup* aLoadGroup) { 389 MOZ_ASSERT(NS_IsMainThread()); 390 391 if (!aLoadGroup) { 392 return; 393 } 394 395 nsCOMPtr<nsIInterfaceRequestor> callbacks; 396 aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); 397 if (!callbacks) { 398 return; 399 } 400 401 nsCOMPtr<nsIBrowserChild> browserChild; 402 callbacks->GetInterface(NS_GET_IID(nsIBrowserChild), 403 getter_AddRefs(browserChild)); 404 if (!browserChild) { 405 return; 406 } 407 408 // Use weak references to the tab child. Holding a strong reference will 409 // not prevent an ActorDestroy() from being called on the BrowserChild. 410 // Therefore, we should let the BrowserChild destroy itself as soon as 411 // possible. 412 mBrowserChildList.AppendElement(do_GetWeakReference(browserChild)); 413 } 414 415 NS_IMETHODIMP 416 WorkerLoadInfo::InterfaceRequestor::GetInterface(const nsIID& aIID, 417 void** aSink) { 418 MOZ_ASSERT(NS_IsMainThread()); 419 MOZ_ASSERT(mLoadContext); 420 421 if (aIID.Equals(NS_GET_IID(nsILoadContext))) { 422 nsCOMPtr<nsILoadContext> ref = mLoadContext; 423 ref.forget(aSink); 424 return NS_OK; 425 } 426 427 // If we still have an active nsIBrowserChild, then return it. Its possible, 428 // though, that all of the BrowserChild objects have been destroyed. In that 429 // case we return NS_NOINTERFACE. 430 if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) { 431 nsCOMPtr<nsIBrowserChild> browserChild = GetAnyLiveBrowserChild(); 432 if (!browserChild) { 433 return NS_NOINTERFACE; 434 } 435 browserChild.forget(aSink); 436 return NS_OK; 437 } 438 439 if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) && 440 mOuterRequestor) { 441 // If asked for the network intercept controller, ask the outer requestor, 442 // which could be the docshell. 443 return mOuterRequestor->GetInterface(aIID, aSink); 444 } 445 446 return NS_NOINTERFACE; 447 } 448 449 already_AddRefed<nsIBrowserChild> 450 WorkerLoadInfo::InterfaceRequestor::GetAnyLiveBrowserChild() { 451 MOZ_ASSERT(NS_IsMainThread()); 452 453 // Search our list of known BrowserChild objects for one that still exists. 454 while (!mBrowserChildList.IsEmpty()) { 455 nsCOMPtr<nsIBrowserChild> browserChild = 456 do_QueryReferent(mBrowserChildList.LastElement()); 457 458 // Does this tab child still exist? If so, return it. We are done. If the 459 // PBrowser actor is no longer useful, don't bother returning this tab. 460 if (browserChild && 461 !static_cast<BrowserChild*>(browserChild.get())->IsDestroyed()) { 462 return browserChild.forget(); 463 } 464 465 // Otherwise remove the stale weak reference and check the next one 466 mBrowserChildList.RemoveLastElement(); 467 } 468 469 return nullptr; 470 } 471 472 NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor) 473 NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor) 474 NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor, 475 nsIInterfaceRequestor) 476 477 WorkerLoadInfo::WorkerLoadInfo() { MOZ_COUNT_CTOR(WorkerLoadInfo); } 478 479 WorkerLoadInfo::WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept 480 : WorkerLoadInfoData(std::move(aOther)) { 481 MOZ_COUNT_CTOR(WorkerLoadInfo); 482 } 483 484 WorkerLoadInfo::~WorkerLoadInfo() { MOZ_COUNT_DTOR(WorkerLoadInfo); } 485 486 } // namespace dom 487 } // namespace mozilla