summaryrefslogtreecommitdiffstats
path: root/dom/workers/WorkerLoadInfo.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/workers/WorkerLoadInfo.cpp')
-rw-r--r--dom/workers/WorkerLoadInfo.cpp507
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