diff options
Diffstat (limited to 'dom/workers/WorkerLoadInfo.cpp')
-rw-r--r-- | dom/workers/WorkerLoadInfo.cpp | 507 |
1 files changed, 507 insertions, 0 deletions
diff --git a/dom/workers/WorkerLoadInfo.cpp b/dom/workers/WorkerLoadInfo.cpp new file mode 100644 index 0000000000..6fc0632b54 --- /dev/null +++ b/dom/workers/WorkerLoadInfo.cpp @@ -0,0 +1,507 @@ +/* -*- 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), + mReferrerInfo(new ReferrerInfo(nullptr)), + mFromWindow(false), + mEvalAllowed(false), + mReportEvalCSPViolations(false), + mWasmEvalAllowed(false), + mReportWasmEvalCSPViolations(false), + mXHRParamsAllowed(false), + mWatchedByDevTools(false), + mStorageAccess(StorageAccess::eDeny), + mUseRegularPrincipal(false), + mHasStorageAccessPermissionGranted(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 |