/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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 "WorkerLoadInfo.h" #include "WorkerPrivate.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/ReferrerInfo.h" #include "mozilla/ipc/BackgroundUtils.h" #include "mozilla/ipc/PBackgroundSharedTypes.h" #include "mozilla/LoadContext.h" #include "mozilla/StorageAccess.h" #include "mozilla/StoragePrincipalHelper.h" #include "nsContentUtils.h" #include "nsIContentSecurityPolicy.h" #include "nsICookieJarSettings.h" #include "nsINetworkInterceptController.h" #include "nsIProtocolHandler.h" #include "nsIReferrerInfo.h" #include "nsIBrowserChild.h" #include "nsScriptSecurityManager.h" #include "nsNetUtil.h" namespace mozilla { using namespace ipc; namespace dom { namespace { class MainThreadReleaseRunnable final : public Runnable { nsTArray<nsCOMPtr<nsISupports>> mDoomed; nsCOMPtr<nsILoadGroup> mLoadGroupToCancel; public: MainThreadReleaseRunnable(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed, nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel) : mozilla::Runnable("MainThreadReleaseRunnable"), mDoomed(std::move(aDoomed)), mLoadGroupToCancel(std::move(aLoadGroupToCancel)) {} NS_INLINE_DECL_REFCOUNTING_INHERITED(MainThreadReleaseRunnable, Runnable) NS_IMETHOD Run() override { if (mLoadGroupToCancel) { mLoadGroupToCancel->CancelWithReason( NS_BINDING_ABORTED, "WorkerLoadInfo::MainThreadReleaseRunnable"_ns); mLoadGroupToCancel = nullptr; } mDoomed.Clear(); return NS_OK; } private: ~MainThreadReleaseRunnable() = default; }; // Specialize this if there's some class that has multiple nsISupports bases. template <class T> struct ISupportsBaseInfo { using ISupportsBase = T; }; template <template <class> class SmartPtr, class T> inline void SwapToISupportsArray(SmartPtr<T>& aSrc, nsTArray<nsCOMPtr<nsISupports>>& aDest) { nsCOMPtr<nsISupports>* dest = aDest.AppendElement(); T* raw = nullptr; aSrc.swap(raw); nsISupports* rawSupports = static_cast<typename ISupportsBaseInfo<T>::ISupportsBase*>(raw); dest->swap(rawSupports); } } // namespace WorkerLoadInfoData::WorkerLoadInfoData() : mLoadFlags(nsIRequest::LOAD_NORMAL), mWindowID(UINT64_MAX), mAssociatedBrowsingContextID(0), mReferrerInfo(new ReferrerInfo(nullptr)), mFromWindow(false), mEvalAllowed(false), mReportEvalCSPViolations(false), mWasmEvalAllowed(false), mReportWasmEvalCSPViolations(false), mXHRParamsAllowed(false), mWatchedByDevTools(false), mStorageAccess(StorageAccess::eDeny), mUseRegularPrincipal(false), mUsingStorageAccess(false), mServiceWorkersTestingInWindow(false), mShouldResistFingerprinting(false), mIsThirdPartyContextToTopWindow(true), mSecureContext(eNotSet) {} nsresult WorkerLoadInfo::SetPrincipalsAndCSPOnMainThread( nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, nsILoadGroup* aLoadGroup, nsIContentSecurityPolicy* aCsp) { AssertIsOnMainThread(); MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(aLoadGroup, aPrincipal)); mPrincipal = aPrincipal; mPartitionedPrincipal = aPartitionedPrincipal; mCSP = aCsp; if (mCSP) { mCSP->GetAllowsEval(&mReportEvalCSPViolations, &mEvalAllowed); mCSP->GetAllowsWasmEval(&mReportWasmEvalCSPViolations, &mWasmEvalAllowed); mCSPInfo = MakeUnique<CSPInfo>(); nsresult rv = CSPToCSPInfo(aCsp, mCSPInfo.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } else { mEvalAllowed = true; mReportEvalCSPViolations = false; mWasmEvalAllowed = true; mReportWasmEvalCSPViolations = false; } mLoadGroup = aLoadGroup; mPrincipalInfo = MakeUnique<PrincipalInfo>(); mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>(); StoragePrincipalHelper::GetRegularPrincipalOriginAttributes( aLoadGroup, mOriginAttributes); nsresult rv = PrincipalToPrincipalInfo(aPrincipal, mPrincipalInfo.get()); NS_ENSURE_SUCCESS(rv, rv); if (aPrincipal->Equals(aPartitionedPrincipal)) { *mPartitionedPrincipalInfo = *mPrincipalInfo; } else { mPartitionedPrincipalInfo = MakeUnique<PrincipalInfo>(); rv = PrincipalToPrincipalInfo(aPartitionedPrincipal, mPartitionedPrincipalInfo.get()); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult WorkerLoadInfo::GetPrincipalsAndLoadGroupFromChannel( nsIChannel* aChannel, nsIPrincipal** aPrincipalOut, nsIPrincipal** aPartitionedPrincipalOut, nsILoadGroup** aLoadGroupOut) { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(aChannel); MOZ_DIAGNOSTIC_ASSERT(aPrincipalOut); MOZ_DIAGNOSTIC_ASSERT(aPartitionedPrincipalOut); MOZ_DIAGNOSTIC_ASSERT(aLoadGroupOut); // Initial triggering principal should be set NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_DOM_INVALID_STATE_ERR); nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); MOZ_DIAGNOSTIC_ASSERT(ssm); nsCOMPtr<nsIPrincipal> channelPrincipal; nsCOMPtr<nsIPrincipal> channelPartitionedPrincipal; nsresult rv = ssm->GetChannelResultPrincipals( aChannel, getter_AddRefs(channelPrincipal), getter_AddRefs(channelPartitionedPrincipal)); NS_ENSURE_SUCCESS(rv, rv); // Every time we call GetChannelResultPrincipal() it will return a different // null principal for a data URL. We don't want to change the worker's // principal again, though. Instead just keep the original null principal we // first got from the channel. // // Note, we don't do this by setting principalToInherit on the channel's // load info because we don't yet have the first null principal when we // create the channel. if (mPrincipal && mPrincipal->GetIsNullPrincipal() && channelPrincipal->GetIsNullPrincipal()) { channelPrincipal = mPrincipal; channelPartitionedPrincipal = mPrincipal; } nsCOMPtr<nsILoadGroup> channelLoadGroup; rv = aChannel->GetLoadGroup(getter_AddRefs(channelLoadGroup)); NS_ENSURE_SUCCESS(rv, rv); MOZ_ASSERT(channelLoadGroup); // If the loading principal is the system principal then the channel // principal must also be the system principal (we do not allow chrome // code to create workers with non-chrome scripts, and if we ever decide // to change this we need to make sure we don't always set // mPrincipalIsSystem to true in WorkerPrivate::GetLoadInfo()). Otherwise // this channel principal must be same origin with the load principal (we // check again here in case redirects changed the location of the script). if (mLoadingPrincipal->IsSystemPrincipal()) { if (!channelPrincipal->IsSystemPrincipal()) { nsCOMPtr<nsIURI> finalURI; rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(finalURI)); NS_ENSURE_SUCCESS(rv, rv); // See if this is a resource URI. Since JSMs usually come from // resource:// URIs we're currently considering all URIs with the // URI_IS_UI_RESOURCE flag as valid for creating privileged workers. bool isResource; rv = NS_URIChainHasFlags(finalURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource); NS_ENSURE_SUCCESS(rv, rv); if (isResource) { // Assign the system principal to the resource:// worker only if it // was loaded from code using the system principal. channelPrincipal = mLoadingPrincipal; channelPartitionedPrincipal = mLoadingPrincipal; } else { return NS_ERROR_DOM_BAD_URI; } } } // The principal can change, but it should still match the original // load group's browser element flag. MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal)); channelPrincipal.forget(aPrincipalOut); channelPartitionedPrincipal.forget(aPartitionedPrincipalOut); channelLoadGroup.forget(aLoadGroupOut); return NS_OK; } nsresult WorkerLoadInfo::SetPrincipalsAndCSPFromChannel(nsIChannel* aChannel) { AssertIsOnMainThread(); nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIPrincipal> partitionedPrincipal; nsCOMPtr<nsILoadGroup> loadGroup; nsresult rv = GetPrincipalsAndLoadGroupFromChannel( aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal), getter_AddRefs(loadGroup)); NS_ENSURE_SUCCESS(rv, rv); // Workers themselves can have their own CSP - Workers of an opaque origin // however inherit the CSP of the document that spawned the worker. nsCOMPtr<nsIContentSecurityPolicy> csp; if (CSP_ShouldResponseInheritCSP(aChannel)) { nsCOMPtr<nsILoadInfo> loadinfo = aChannel->LoadInfo(); csp = loadinfo->GetCsp(); } return SetPrincipalsAndCSPOnMainThread(principal, partitionedPrincipal, loadGroup, csp); } bool WorkerLoadInfo::FinalChannelPrincipalIsValid(nsIChannel* aChannel) { AssertIsOnMainThread(); nsCOMPtr<nsIPrincipal> principal; nsCOMPtr<nsIPrincipal> partitionedPrincipal; nsCOMPtr<nsILoadGroup> loadGroup; nsresult rv = GetPrincipalsAndLoadGroupFromChannel( aChannel, getter_AddRefs(principal), getter_AddRefs(partitionedPrincipal), getter_AddRefs(loadGroup)); NS_ENSURE_SUCCESS(rv, false); // Verify that the channel is still a null principal. We don't care // if these are the exact same null principal object, though. From // the worker's perspective its the same effect. if (principal->GetIsNullPrincipal() && mPrincipal->GetIsNullPrincipal()) { return true; } // Otherwise we require exact equality. Redirects can happen, but they // are not allowed to change our principal. if (principal->Equals(mPrincipal)) { return true; } return false; } #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED bool WorkerLoadInfo::PrincipalIsValid() const { return mPrincipal && mPrincipalInfo && mPrincipalInfo->type() != PrincipalInfo::T__None && mPrincipalInfo->type() <= PrincipalInfo::T__Last && mPartitionedPrincipal && mPartitionedPrincipalInfo && mPartitionedPrincipalInfo->type() != PrincipalInfo::T__None && mPartitionedPrincipalInfo->type() <= PrincipalInfo::T__Last; } bool WorkerLoadInfo::PrincipalURIMatchesScriptURL() { AssertIsOnMainThread(); nsAutoCString scheme; nsresult rv = mBaseURI->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, false); // A system principal must either be a blob URL or a resource JSM. if (mPrincipal->IsSystemPrincipal()) { if (scheme == "blob"_ns) { return true; } bool isResource = false; nsresult rv = NS_URIChainHasFlags( mBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &isResource); NS_ENSURE_SUCCESS(rv, false); return isResource; } // A null principal can occur for a data URL worker script or a blob URL // worker script from a sandboxed iframe. if (mPrincipal->GetIsNullPrincipal()) { return scheme == "data"_ns || scheme == "blob"_ns; } // The principal for a blob: URL worker script does not have a matching URL. // This is likely a bug in our referer setting logic, but exempt it for now. // This is another reason we should fix bug 1340694 so that referer does not // depend on the principal URI. if (scheme == "blob"_ns) { return true; } if (mPrincipal->IsSameOrigin(mBaseURI)) { return true; } // If strict file origin policy is in effect, local files will always fail // IsSameOrigin unless they are identical. Explicitly check file origin // policy, in that case. bool allowsRelaxedOriginPolicy = false; rv = mPrincipal->AllowsRelaxStrictFileOriginPolicy( mBaseURI, &allowsRelaxedOriginPolicy); if (nsScriptSecurityManager::GetStrictFileOriginPolicy() && NS_URIIsLocalFile(mBaseURI) && (NS_SUCCEEDED(rv) && allowsRelaxedOriginPolicy)) { return true; } return false; } #endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED bool WorkerLoadInfo::ProxyReleaseMainThreadObjects( WorkerPrivate* aWorkerPrivate) { nsCOMPtr<nsILoadGroup> nullLoadGroup; return ProxyReleaseMainThreadObjects(aWorkerPrivate, std::move(nullLoadGroup)); } bool WorkerLoadInfo::ProxyReleaseMainThreadObjects( WorkerPrivate* aWorkerPrivate, nsCOMPtr<nsILoadGroup>&& aLoadGroupToCancel) { static const uint32_t kDoomedCount = 11; nsTArray<nsCOMPtr<nsISupports>> doomed(kDoomedCount); SwapToISupportsArray(mWindow, doomed); SwapToISupportsArray(mScriptContext, doomed); SwapToISupportsArray(mBaseURI, doomed); SwapToISupportsArray(mResolvedScriptURI, doomed); SwapToISupportsArray(mPrincipal, doomed); SwapToISupportsArray(mPartitionedPrincipal, doomed); SwapToISupportsArray(mLoadingPrincipal, doomed); SwapToISupportsArray(mChannel, doomed); SwapToISupportsArray(mCSP, doomed); SwapToISupportsArray(mLoadGroup, doomed); SwapToISupportsArray(mInterfaceRequestor, doomed); // Before adding anything here update kDoomedCount above! MOZ_ASSERT(doomed.Length() == kDoomedCount); RefPtr<MainThreadReleaseRunnable> runnable = new MainThreadReleaseRunnable( std::move(doomed), std::move(aLoadGroupToCancel)); return NS_SUCCEEDED(aWorkerPrivate->DispatchToMainThread(runnable.forget())); } WorkerLoadInfo::InterfaceRequestor::InterfaceRequestor( nsIPrincipal* aPrincipal, nsILoadGroup* aLoadGroup) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aPrincipal); // Look for an existing LoadContext. This is optional and it's ok if // we don't find one. nsCOMPtr<nsILoadContext> baseContext; if (aLoadGroup) { nsCOMPtr<nsIInterfaceRequestor> callbacks; aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); if (callbacks) { callbacks->GetInterface(NS_GET_IID(nsILoadContext), getter_AddRefs(baseContext)); } mOuterRequestor = callbacks; } mLoadContext = new LoadContext(aPrincipal, baseContext); } void WorkerLoadInfo::InterfaceRequestor::MaybeAddBrowserChild( nsILoadGroup* aLoadGroup) { MOZ_ASSERT(NS_IsMainThread()); if (!aLoadGroup) { return; } nsCOMPtr<nsIInterfaceRequestor> callbacks; aLoadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks)); if (!callbacks) { return; } nsCOMPtr<nsIBrowserChild> browserChild; callbacks->GetInterface(NS_GET_IID(nsIBrowserChild), getter_AddRefs(browserChild)); if (!browserChild) { return; } // Use weak references to the tab child. Holding a strong reference will // not prevent an ActorDestroy() from being called on the BrowserChild. // Therefore, we should let the BrowserChild destroy itself as soon as // possible. mBrowserChildList.AppendElement(do_GetWeakReference(browserChild)); } NS_IMETHODIMP WorkerLoadInfo::InterfaceRequestor::GetInterface(const nsIID& aIID, void** aSink) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mLoadContext); if (aIID.Equals(NS_GET_IID(nsILoadContext))) { nsCOMPtr<nsILoadContext> ref = mLoadContext; ref.forget(aSink); return NS_OK; } // If we still have an active nsIBrowserChild, then return it. Its possible, // though, that all of the BrowserChild objects have been destroyed. In that // case we return NS_NOINTERFACE. if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) { nsCOMPtr<nsIBrowserChild> browserChild = GetAnyLiveBrowserChild(); if (!browserChild) { return NS_NOINTERFACE; } browserChild.forget(aSink); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) && mOuterRequestor) { // If asked for the network intercept controller, ask the outer requestor, // which could be the docshell. return mOuterRequestor->GetInterface(aIID, aSink); } return NS_NOINTERFACE; } already_AddRefed<nsIBrowserChild> WorkerLoadInfo::InterfaceRequestor::GetAnyLiveBrowserChild() { MOZ_ASSERT(NS_IsMainThread()); // Search our list of known BrowserChild objects for one that still exists. while (!mBrowserChildList.IsEmpty()) { nsCOMPtr<nsIBrowserChild> browserChild = do_QueryReferent(mBrowserChildList.LastElement()); // Does this tab child still exist? If so, return it. We are done. If the // PBrowser actor is no longer useful, don't bother returning this tab. if (browserChild && !static_cast<BrowserChild*>(browserChild.get())->IsDestroyed()) { return browserChild.forget(); } // Otherwise remove the stale weak reference and check the next one mBrowserChildList.RemoveLastElement(); } return nullptr; } NS_IMPL_ADDREF(WorkerLoadInfo::InterfaceRequestor) NS_IMPL_RELEASE(WorkerLoadInfo::InterfaceRequestor) NS_IMPL_QUERY_INTERFACE(WorkerLoadInfo::InterfaceRequestor, nsIInterfaceRequestor) WorkerLoadInfo::WorkerLoadInfo() { MOZ_COUNT_CTOR(WorkerLoadInfo); } WorkerLoadInfo::WorkerLoadInfo(WorkerLoadInfo&& aOther) noexcept : WorkerLoadInfoData(std::move(aOther)) { MOZ_COUNT_CTOR(WorkerLoadInfo); } WorkerLoadInfo::~WorkerLoadInfo() { MOZ_COUNT_DTOR(WorkerLoadInfo); } } // namespace dom } // namespace mozilla