Local Network Access (LNA) checks compare the parent document's IP address
space against the target server to detect cross-network-boundary requests
(e.g., a public website fetching from localhost). Previously, LNA was only
enforced for requests from document contexts. Worker-initiated requests
(fetch, XHR) bypassed LNA entirely because workers had no IP address space
from their parent document.
This patch propagates the document's IP address space to all worker types
and enforces LNA checks with worker-type-specific permission behavior:
Dedicated workers:
- IP address space is read directly from the document's PolicyContainer
during WorkerPrivate::GetLoadInfo() and stored in WorkerLoadInfo.
- LNA permission prompts ARE shown to the user via the parent document's
browsing context (AssociatedBrowsingContextID points to the creating tab).
- Behavior matches document-context LNA: user can Allow or Deny, and the
decision is cached as a site permission.
Shared workers:
- IP address space is carried via the ClientInfo's PolicyContainerArgs in
RemoteWorkerData, which crosses process boundaries via IPC.
- RemoteWorkerChild::ExecWorkerOnMainThread() now extracts ipAddressSpace
from PolicyContainerArgs (previously only CSP was extracted).
- LNA permission prompts are NOT shown. Shared workers may run in a
different content process and are not tied to a single tab, so there is
no browsing context to anchor a prompt to.
- Access is granted only if a persistent ALLOW permission already exists
for the origin (e.g., previously granted via a document-context prompt).
Otherwise the request is denied silently.
Service workers:
- IP address space is captured from the registering document's ClientInfo
during ServiceWorkerManager::Register() and stored on
ServiceWorkerRegistrationInfo. It is propagated to RemoteWorkerData via
RefreshRemoteWorkerData() before every spawn.
- Both Initialize() and RefreshRemoteWorkerData() set ipAddressSpace
because Register() (which captures the value) and Initialize() (which
builds RemoteWorkerData) can run in either order depending on whether
the registration is new or pre-existing.
- LNA permission prompts are NOT shown. Service workers are event-driven,
have no associated browsing context, and can be spawned long after the
registering document is gone.
- Same permission behavior as shared workers: access requires a persistent
ALLOW permission, otherwise denied silently.
Differential Revision: https://phabricator.services.mozilla.com/D288651
384 lines
14 KiB
C++
384 lines
14 KiB
C++
/* 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/. */
|
|
|
|
#ifndef mozilla_dom_workers_scriptloader_h_
|
|
#define mozilla_dom_workers_scriptloader_h_
|
|
|
|
#include "js/loader/ModuleLoaderBase.h"
|
|
#include "js/loader/ScriptLoadRequest.h"
|
|
#include "js/loader/ScriptLoadRequestList.h"
|
|
#include "mozilla/ErrorResult.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/dom/WorkerBinding.h"
|
|
#include "mozilla/dom/WorkerCommon.h"
|
|
#include "mozilla/dom/WorkerLoadContext.h"
|
|
#include "mozilla/dom/WorkerRef.h"
|
|
#include "mozilla/dom/workerinternals/WorkerModuleLoader.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsStringFwd.h"
|
|
#include "nsTArrayForwardDeclare.h"
|
|
|
|
class nsIChannel;
|
|
class nsICookieJarSettings;
|
|
class nsILoadGroup;
|
|
class nsIPrincipal;
|
|
class nsIReferrerInfo;
|
|
class nsIURI;
|
|
|
|
namespace mozilla {
|
|
|
|
namespace dom {
|
|
|
|
class ClientInfo;
|
|
class Document;
|
|
struct WorkerLoadInfo;
|
|
class WorkerPrivate;
|
|
class SerializedStackHolder;
|
|
|
|
enum WorkerScriptType { WorkerScript, DebuggerScript };
|
|
|
|
namespace workerinternals {
|
|
|
|
namespace loader {
|
|
class ScriptExecutorRunnable;
|
|
class ScriptLoaderRunnable;
|
|
class CachePromiseHandler;
|
|
class CacheLoadHandler;
|
|
class CacheCreator;
|
|
class NetworkLoadHandler;
|
|
|
|
/*
|
|
* [DOMDOC] WorkerScriptLoader
|
|
*
|
|
* The WorkerScriptLoader is the primary class responsible for loading all
|
|
* Workers, including: ServiceWorkers, SharedWorkers, RemoteWorkers, and
|
|
* dedicated Workers. Our implementation also includes a subtype of dedicated
|
|
* workers: ChromeWorker, which exposes information that isn't normally
|
|
* accessible on a dedicated worker. See [1] for more information.
|
|
*
|
|
* Due to constraints around fetching, this class currently delegates the
|
|
* "Fetch" portion of its work load to the main thread. Unlike the DOM
|
|
* ScriptLoader, the WorkerScriptLoader is not persistent and is not reused for
|
|
* subsequent loads. That means for each iteration of loading (for example,
|
|
* loading the main script, followed by a load triggered by ImportScripts), we
|
|
* recreate this class, and handle the case independently.
|
|
*
|
|
* The flow of requests across the boundaries looks like this:
|
|
*
|
|
* +----------------------------+
|
|
* | new WorkerScriptLoader(..) |
|
|
* +----------------------------+
|
|
* |
|
|
* V
|
|
* +-------------------------------------------+
|
|
* | WorkerScriptLoader::DispatchLoadScripts() |
|
|
* +-------------------------------------------+
|
|
* |
|
|
* V
|
|
* +............................+
|
|
* | new ScriptLoaderRunnable() |
|
|
* +............................+
|
|
* :
|
|
* V
|
|
* #####################################################################
|
|
* Enter Main thread
|
|
* #####################################################################
|
|
* :
|
|
* V
|
|
* +.............................+ For each: Is a normal Worker?
|
|
* | ScriptLoaderRunnable::Run() |----------------------------------+
|
|
* +.............................+ |
|
|
* | V
|
|
* | +----------------------------------+
|
|
* | | WorkerScriptLoader::LoadScript() |
|
|
* | +----------------------------------+
|
|
* | |
|
|
* | For each request: Is a ServiceWorker? |
|
|
* | |
|
|
* V V
|
|
* +==================+ No script in cache? +====================+
|
|
* | CacheLoadHandler |------------------------>| NetworkLoadHandler |
|
|
* +==================+ +====================+
|
|
* : :
|
|
* : Loaded from Cache : Loaded by Network
|
|
* : +..........................................+ :
|
|
* +---| ScriptLoaderRunnable::OnStreamComplete() |<----+
|
|
* +..........................................+
|
|
* |
|
|
* | A request is ready, is it in post order?
|
|
* |
|
|
* | call DispatchPendingProcessRequests()
|
|
* | This creates ScriptExecutorRunnable
|
|
* +..............................+
|
|
* | new ScriptLoaderExecutable() |
|
|
* +..............................+
|
|
* :
|
|
* V
|
|
* #####################################################################
|
|
* Enter worker thread
|
|
* #####################################################################
|
|
* :
|
|
* V
|
|
* +...............................+ All Scripts Executed?
|
|
* | ScriptLoaderExecutable::Run() | -------------+
|
|
* +...............................+ :
|
|
* :
|
|
* : yes. Do execution
|
|
* : then shutdown.
|
|
* :
|
|
* V
|
|
* +--------------------------------------------+
|
|
* | WorkerScriptLoader::ShutdownScriptLoader() |
|
|
* +--------------------------------------------+
|
|
*/
|
|
|
|
class WorkerScriptLoader : public JS::loader::ScriptLoaderInterface,
|
|
public nsINamed {
|
|
friend class ScriptExecutorRunnable;
|
|
friend class ScriptLoaderRunnable;
|
|
friend class CachePromiseHandler;
|
|
friend class CacheLoadHandler;
|
|
friend class CacheCreator;
|
|
friend class NetworkLoadHandler;
|
|
friend class WorkerModuleLoader;
|
|
|
|
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
|
|
UniquePtr<SerializedStackHolder> mOriginStack;
|
|
nsString mOriginStackJSON;
|
|
nsCOMPtr<nsISerialEventTarget> mSyncLoopTarget;
|
|
ScriptLoadRequestList mLoadingRequests;
|
|
ScriptLoadRequestList mLoadedRequests;
|
|
Maybe<ServiceWorkerDescriptor> mController;
|
|
WorkerScriptType mWorkerScriptType;
|
|
// Stores error raised by the loader. Callers that care about
|
|
// the result pull it out via TakeErrorResult() on the worker thread;
|
|
// anything still pending at destruction is suppressed.
|
|
ErrorResult mRv;
|
|
bool mExecutionAborted = false;
|
|
bool mMutedErrorFlag = false;
|
|
|
|
// Count of loading module requests. mLoadingRequests doesn't keep track of
|
|
// child module requests.
|
|
// This member should be accessed on worker thread.
|
|
uint32_t mLoadingModuleRequestCount;
|
|
|
|
// Worker cancellation related Mutex
|
|
//
|
|
// Modified on the worker thread.
|
|
// It is ok to *read* this without a lock on the worker.
|
|
// Main thread must always acquire a lock.
|
|
bool mCleanedUp MOZ_GUARDED_BY(
|
|
mCleanUpLock); // To specify if the cleanUp() has been done.
|
|
|
|
Mutex& CleanUpLock() MOZ_RETURN_CAPABILITY(mCleanUpLock) {
|
|
return mCleanUpLock;
|
|
}
|
|
|
|
bool CleanedUp() const MOZ_REQUIRES(mCleanUpLock) {
|
|
mCleanUpLock.AssertCurrentThreadOwns();
|
|
return mCleanedUp;
|
|
}
|
|
|
|
// Ensure the worker and the main thread won't race to access |mCleanedUp|.
|
|
// This should perhaps be a EventTargetAndLockCapability to model the
|
|
// reader/writer behaviour on mCleanedUp better.
|
|
Mutex mCleanUpLock;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
static already_AddRefed<WorkerScriptLoader> Create(
|
|
WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
nsISerialEventTarget* aSyncLoopTarget,
|
|
WorkerScriptType aWorkerScriptType);
|
|
|
|
ErrorResult TakeErrorResult();
|
|
|
|
bool CreateScriptRequests(const nsTArray<nsString>& aScriptURLs,
|
|
const mozilla::Encoding* aDocumentEncoding,
|
|
bool aIsMainScript);
|
|
|
|
ScriptLoadRequest* GetMainScript();
|
|
|
|
already_AddRefed<ScriptLoadRequest> CreateScriptLoadRequest(
|
|
const nsString& aScriptURL, const mozilla::Encoding* aDocumentEncoding,
|
|
bool aIsMainScript, nsresult* aRv);
|
|
|
|
bool DispatchLoadScript(ScriptLoadRequest* aRequest);
|
|
|
|
bool DispatchLoadScripts(
|
|
nsTArray<RefPtr<ThreadSafeRequestHandle>>&& aLoadingList = {});
|
|
|
|
void TryShutdown();
|
|
|
|
WorkerScriptType GetWorkerScriptType() { return mWorkerScriptType; }
|
|
|
|
protected:
|
|
nsIURI* GetBaseURI() const override;
|
|
|
|
nsIURI* GetInitialBaseURI();
|
|
|
|
nsIGlobalObject* GetGlobal();
|
|
|
|
void MaybeMoveToLoadedList(ScriptLoadRequest* aRequest);
|
|
|
|
bool StorePolicyContainerArgs();
|
|
|
|
bool ProcessPendingRequests(JSContext* aCx);
|
|
|
|
bool AllScriptsExecuted() {
|
|
return mLoadingRequests.isEmpty() && mLoadedRequests.isEmpty();
|
|
}
|
|
|
|
bool IsDebuggerScript() const { return mWorkerScriptType == DebuggerScript; }
|
|
|
|
void SetController(const Maybe<ServiceWorkerDescriptor>& aDescriptor) {
|
|
mController = aDescriptor;
|
|
}
|
|
|
|
Maybe<ServiceWorkerDescriptor>& GetController() { return mController; }
|
|
|
|
nsresult LoadScript(ThreadSafeRequestHandle* aRequestHandle);
|
|
|
|
void ShutdownScriptLoader(bool aResult, bool aMutedError);
|
|
|
|
private:
|
|
WorkerScriptLoader(UniquePtr<SerializedStackHolder> aOriginStack,
|
|
nsISerialEventTarget* aSyncLoopTarget,
|
|
WorkerScriptType aWorkerScriptType);
|
|
|
|
~WorkerScriptLoader();
|
|
|
|
NS_IMETHOD
|
|
GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("WorkerScriptLoader");
|
|
return NS_OK;
|
|
}
|
|
|
|
void InitModuleLoader();
|
|
|
|
nsTArray<RefPtr<ThreadSafeRequestHandle>> GetLoadingList();
|
|
|
|
bool IsDynamicImport(ScriptLoadRequest* aRequest);
|
|
|
|
nsContentPolicyType GetContentPolicyType(ScriptLoadRequest* aRequest);
|
|
|
|
bool EvaluateScript(JSContext* aCx, ScriptLoadRequest* aRequest);
|
|
|
|
nsresult FillCompileOptionsForRequest(
|
|
JSContext* cx, ScriptLoadRequest* aRequest, JS::CompileOptions* aOptions,
|
|
JS::MutableHandle<JSScript*> aIntroductionScript) override;
|
|
|
|
void ReportErrorToConsole(ScriptLoadRequest* aRequest,
|
|
nsresult aResult) const override;
|
|
|
|
// Only used by import maps, crash if we get here.
|
|
void ReportWarningToConsole(
|
|
ScriptLoadRequest* aRequest, const char* aMessageName,
|
|
const nsTArray<nsString>& aParams = nsTArray<nsString>()) const override {
|
|
MOZ_CRASH("Import maps have not been implemented for this context");
|
|
}
|
|
|
|
void LogExceptionToConsole(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
|
|
|
|
bool AllModuleRequestsLoaded() const;
|
|
void IncreaseLoadingModuleRequestCount();
|
|
void DecreaseLoadingModuleRequestCount();
|
|
};
|
|
|
|
/* ScriptLoaderRunnable
|
|
*
|
|
* Responsibilities of this class:
|
|
* - the actual dispatch
|
|
* - delegating the load back to WorkerScriptLoader
|
|
* - handling the collections of scripts being requested
|
|
* - handling main thread cancellation
|
|
* - dispatching back to the worker thread
|
|
*/
|
|
class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
|
|
RefPtr<WorkerScriptLoader> mScriptLoader;
|
|
RefPtr<ThreadSafeWorkerRef> mWorkerRef;
|
|
nsTArrayView<RefPtr<ThreadSafeRequestHandle>> mLoadingRequests;
|
|
Maybe<nsresult> mCancelMainThread;
|
|
RefPtr<CacheCreator> mCacheCreator;
|
|
|
|
public:
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
explicit ScriptLoaderRunnable(
|
|
WorkerScriptLoader* aScriptLoader,
|
|
nsTArray<RefPtr<ThreadSafeRequestHandle>> aLoadingRequests);
|
|
|
|
nsresult OnStreamComplete(ThreadSafeRequestHandle* aRequestHandle,
|
|
nsresult aStatus);
|
|
|
|
void LoadingFinished(ThreadSafeRequestHandle* aRequestHandle, nsresult aRv);
|
|
|
|
void MaybeExecuteFinishedScripts(ThreadSafeRequestHandle* aRequestHandle);
|
|
|
|
bool IsCancelled() { return mCancelMainThread.isSome(); }
|
|
|
|
nsresult GetCancelResult() {
|
|
return (IsCancelled()) ? mCancelMainThread.ref() : NS_OK;
|
|
}
|
|
|
|
void CancelMainThreadWithBindingAborted();
|
|
|
|
CacheCreator* GetCacheCreator() { return mCacheCreator; };
|
|
|
|
private:
|
|
~ScriptLoaderRunnable() = default;
|
|
|
|
void CancelMainThread(nsresult aCancelResult);
|
|
|
|
void DispatchProcessPendingRequests();
|
|
|
|
NS_IMETHOD
|
|
Run() override;
|
|
|
|
NS_IMETHOD
|
|
GetName(nsACString& aName) override {
|
|
aName.AssignLiteral("ScriptLoaderRunnable");
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
} // namespace loader
|
|
|
|
nsresult ChannelFromScriptURLMainThread(
|
|
nsIPrincipal* aPrincipal, Document* aParentDoc, nsILoadGroup* aLoadGroup,
|
|
nsIURI* aScriptURL, const WorkerType& aWorkerType,
|
|
const RequestCredentials& aCredentials,
|
|
const Maybe<ClientInfo>& aClientInfo,
|
|
nsContentPolicyType aContentPolicyType,
|
|
nsICookieJarSettings* aCookieJarSettings, nsIReferrerInfo* aReferrerInfo,
|
|
nsIChannel** aChannel);
|
|
|
|
nsresult ChannelFromScriptURLWorkerThread(
|
|
JSContext* aCx, WorkerPrivate* aParent, const nsAString& aScriptURL,
|
|
const WorkerType& aWorkerType, const RequestCredentials& aCredentials,
|
|
WorkerLoadInfo& aLoadInfo);
|
|
|
|
void ReportLoadError(ErrorResult& aRv, nsresult aLoadResult,
|
|
const nsAString& aScriptURL);
|
|
|
|
void LoadMainScript(WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
const nsAString& aScriptURL,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv,
|
|
const mozilla::Encoding* aDocumentEncoding);
|
|
|
|
void Load(WorkerPrivate* aWorkerPrivate,
|
|
UniquePtr<SerializedStackHolder> aOriginStack,
|
|
const nsTArray<nsString>& aScriptURLs,
|
|
WorkerScriptType aWorkerScriptType, ErrorResult& aRv);
|
|
|
|
} // namespace workerinternals
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
#endif /* mozilla_dom_workers_scriptloader_h_ */
|