Bug 1575090 - set COEP for all workers and enforce it when loading Dedicated Workers r=asuth

Differential Revision: https://phabricator.services.mozilla.com/D46177

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Perry Jiang 2019-10-24 15:48:50 +00:00
Родитель eb5d4df193
Коммит 8140fd1dc1
3 изменённых файлов: 247 добавлений и 26 удалений

Просмотреть файл

@ -71,6 +71,8 @@
#include "mozilla/dom/SRILogHelper.h"
#include "mozilla/dom/ServiceWorkerBinding.h"
#include "mozilla/dom/ServiceWorkerManager.h"
#include "mozilla/Result.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/UniquePtr.h"
@ -491,8 +493,8 @@ class CacheScriptLoader final : public PromiseNativeHandler,
ScriptLoadInfo& mLoadInfo;
uint32_t mIndex;
RefPtr<ScriptLoaderRunnable> mRunnable;
bool mIsWorkerScript;
const RefPtr<ScriptLoaderRunnable> mRunnable;
const bool mIsWorkerScript;
bool mFailed;
const ServiceWorkerState mState;
nsCOMPtr<nsIInputStreamPump> mPump;
@ -567,13 +569,86 @@ class LoaderListener final : public nsIStreamLoaderObserver,
NS_IMPL_ISUPPORTS(LoaderListener, nsIStreamLoaderObserver, nsIRequestObserver)
class ScriptResponseHeaderProcessor final : public nsIRequestObserver {
public:
NS_DECL_ISUPPORTS
ScriptResponseHeaderProcessor(WorkerPrivate* aWorkerPrivate,
bool aIsMainScript)
: mWorkerPrivate(aWorkerPrivate), mIsMainScript(aIsMainScript) {
AssertIsOnMainThread();
}
NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
nsresult rv = ProcessCrossOriginEmbedderPolicyHeader(aRequest);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRequest->Cancel(rv);
}
return rv;
}
NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) override {
MOZ_DIAGNOSTIC_ASSERT_IF(NS_SUCCEEDED(aStatusCode),
mWorkerPrivate->GetEmbedderPolicy().isSome());
return NS_OK;
}
static nsresult ProcessCrossOriginEmbedderPolicyHeader(
WorkerPrivate* aWorkerPrivate,
nsILoadInfo::CrossOriginEmbedderPolicy aPolicy, bool aIsMainScript) {
MOZ_ASSERT(aWorkerPrivate);
if (aIsMainScript) {
MOZ_TRY(aWorkerPrivate->SetEmbedderPolicy(aPolicy));
} else if (!aWorkerPrivate->MatchEmbedderPolicy(aPolicy)) {
return NS_ERROR_BLOCKED_BY_POLICY;
}
return NS_OK;
}
private:
~ScriptResponseHeaderProcessor() = default;
nsresult ProcessCrossOriginEmbedderPolicyHeader(nsIRequest* aRequest) {
MOZ_ASSERT_IF(!mIsMainScript, mWorkerPrivate->GetEmbedderPolicy().isSome());
nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aRequest);
// NOTE: the spec doesn't say what to do with non-HTTP workers.
// See: https://github.com/whatwg/html/issues/4916
if (!httpChannel) {
if (mIsMainScript) {
mWorkerPrivate->InheritOwnerEmbedderPolicyOrNull(aRequest);
}
return NS_OK;
}
nsILoadInfo::CrossOriginEmbedderPolicy coep;
MOZ_TRY(httpChannel->GetResponseEmbedderPolicy(&coep));
return ProcessCrossOriginEmbedderPolicyHeader(mWorkerPrivate, coep,
mIsMainScript);
}
WorkerPrivate* const mWorkerPrivate;
const bool mIsMainScript;
};
NS_IMPL_ISUPPORTS(ScriptResponseHeaderProcessor, nsIRequestObserver);
class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
friend class ScriptExecutorRunnable;
friend class CachePromiseHandler;
friend class CacheScriptLoader;
friend class LoaderListener;
WorkerPrivate* mWorkerPrivate;
WorkerPrivate* const mWorkerPrivate;
UniquePtr<SerializedStackHolder> mOriginStack;
nsString mOriginStackJSON;
nsCOMPtr<nsIEventTarget> mSyncLoopTarget;
@ -581,7 +656,7 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
RefPtr<CacheCreator> mCacheCreator;
Maybe<ClientInfo> mClientInfo;
Maybe<ServiceWorkerDescriptor> mController;
bool mIsMainScript;
const bool mIsMainScript;
WorkerScriptType mWorkerScriptType;
bool mCanceledMainThread;
ErrorResult& mRv;
@ -680,13 +755,22 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
}
nsresult OnStartRequest(nsIRequest* aRequest, uint32_t aIndex) {
nsresult rv = OnStartRequestInternal(aRequest, aIndex);
if (NS_WARN_IF(NS_FAILED(rv))) {
aRequest->Cancel(rv);
}
return rv;
}
nsresult OnStartRequestInternal(nsIRequest* aRequest, uint32_t aIndex) {
AssertIsOnMainThread();
MOZ_ASSERT(aIndex < mLoadInfos.Length());
// If one load info cancels or hits an error, it can race with the start
// callback coming from another load info.
if (mCanceledMainThread || !mCacheCreator) {
aRequest->Cancel(NS_ERROR_FAILURE);
return NS_ERROR_FAILURE;
}
@ -715,7 +799,6 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
nsTArray<nsString>{NS_ConvertUTF8toUTF16(scope),
NS_ConvertUTF8toUTF16(mimeType), loadInfo.mURL});
channel->Cancel(NS_ERROR_DOM_NETWORK_ERR);
return NS_ERROR_DOM_NETWORK_ERR;
}
}
@ -745,19 +828,11 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
NS_ASSERTION(ssm, "Should never be null!");
nsCOMPtr<nsIPrincipal> channelPrincipal;
nsresult rv = ssm->GetChannelResultPrincipal(
channel, getter_AddRefs(channelPrincipal));
if (NS_WARN_IF(NS_FAILED(rv))) {
channel->Cancel(rv);
return rv;
}
MOZ_TRY(ssm->GetChannelResultPrincipal(channel,
getter_AddRefs(channelPrincipal)));
UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo());
rv = PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get());
if (NS_WARN_IF(NS_FAILED(rv))) {
channel->Cancel(rv);
return rv;
}
MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get()));
ir->SetPrincipalInfo(std::move(principalInfo));
ir->Headers()->FillResponseHeaders(loadInfo.mChannel);
@ -781,9 +856,7 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
mCacheCreator->Cache_()->Put(jsapi.cx(), request, *response, error);
error.WouldReportJSException();
if (NS_WARN_IF(error.Failed())) {
nsresult rv = error.StealNSResult();
channel->Cancel(rv);
return rv;
return error.StealNSResult();
}
RefPtr<CachePromiseHandler> promiseHandler =
@ -1016,10 +1089,17 @@ class ScriptLoaderRunnable final : public nsIRunnable, public nsINamed {
// where to put the result.
RefPtr<LoaderListener> listener = new LoaderListener(this, aIndex);
// We don't care about progress so just use the simple stream loader for
// OnStreamComplete notification only.
RefPtr<ScriptResponseHeaderProcessor> headerProcessor = nullptr;
// For each debugger script, a non-debugger script load of the same script
// should have occured prior that processed the headers.
if (!IsDebuggerScript()) {
headerProcessor = MakeRefPtr<ScriptResponseHeaderProcessor>(
mWorkerPrivate, mIsMainScript);
}
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), listener);
rv = NS_NewStreamLoader(getter_AddRefs(loader), listener, headerProcessor);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
@ -1749,6 +1829,25 @@ void CacheScriptLoader::ResolvedCallback(JSContext* aCx,
headers->Get(NS_LITERAL_CSTRING("referrer-policy"),
mReferrerPolicyHeaderValue, IgnoreErrors());
nsAutoCString coepHeader;
headers->Get(NS_LITERAL_CSTRING("cross-origin-embedder-policy"), coepHeader,
IgnoreErrors());
nsILoadInfo::CrossOriginEmbedderPolicy coep =
nsILoadInfo::EMBEDDER_POLICY_NULL;
if (coepHeader.EqualsLiteral("require-corp")) {
coep = nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP;
}
rv = ScriptResponseHeaderProcessor::ProcessCrossOriginEmbedderPolicyHeader(
mRunnable->mWorkerPrivate, coep, mRunnable->mIsMainScript);
if (NS_WARN_IF(NS_FAILED(rv))) {
Fail(rv);
return;
}
nsCOMPtr<nsIInputStream> inputStream;
response->GetBody(getter_AddRefs(inputStream));
mChannelInfo = response->GetChannelInfo();

Просмотреть файл

@ -15,6 +15,7 @@
#include "js/SourceText.h"
#include "MessageEventRunnable.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/dom/CallbackDebuggerNotification.h"
@ -4992,6 +4993,84 @@ bool WorkerPrivate::CanShareMemory(const nsID& aAgentClusterId) {
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
}
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> WorkerPrivate::GetEmbedderPolicy()
const {
MOZ_ASSERT(NS_IsMainThread());
if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
return Some(nsILoadInfo::EMBEDDER_POLICY_NULL);
}
return mEmbedderPolicy;
}
Result<Ok, nsresult> WorkerPrivate::SetEmbedderPolicy(
nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) {
MOZ_ASSERT(NS_IsMainThread());
if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
return Ok();
}
if (GetOwnerEmbedderPolicy().valueOr(aPolicy) != aPolicy) {
return Err(NS_ERROR_BLOCKED_BY_POLICY);
}
mEmbedderPolicy.emplace(aPolicy);
return Ok();
}
void WorkerPrivate::InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aRequest);
auto coep = GetOwnerEmbedderPolicy();
if (coep.isSome()) {
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
MOZ_ASSERT(channel);
nsCOMPtr<nsIURI> scriptURI;
MOZ_ALWAYS_SUCCEEDS(channel->GetURI(getter_AddRefs(scriptURI)));
bool isLocalScriptURI = false;
MOZ_ALWAYS_SUCCEEDS(NS_URIChainHasFlags(
scriptURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
&isLocalScriptURI));
MOZ_RELEASE_ASSERT(isLocalScriptURI);
}
mEmbedderPolicy.emplace(coep.valueOr(nsILoadInfo::EMBEDDER_POLICY_NULL));
}
bool WorkerPrivate::MatchEmbedderPolicy(
nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const {
MOZ_ASSERT(NS_IsMainThread());
if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
return true;
}
return mEmbedderPolicy.value() == aPolicy;
}
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>
WorkerPrivate::GetOwnerEmbedderPolicy() const {
MOZ_ASSERT(NS_IsMainThread());
if (GetParent()) {
return GetParent()->GetEmbedderPolicy();
}
if (GetWindow() && GetWindow()->GetBrowsingContext()) {
return Some(GetWindow()->GetBrowsingContext()->GetEmbedderPolicy());
}
return Nothing();
}
NS_IMPL_ADDREF(WorkerPrivate::EventTarget)
NS_IMPL_RELEASE(WorkerPrivate::EventTarget)

Просмотреть файл

@ -7,18 +7,23 @@
#ifndef mozilla_dom_workers_workerprivate_h__
#define mozilla_dom_workers_workerprivate_h__
#include "MainThreadUtils.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerStatus.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/CondVar.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/Maybe.h"
#include "mozilla/MozPromise.h"
#include "mozilla/RelativeTimeline.h"
#include "mozilla/Result.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/ThreadSafeWeakPtr.h"
#include "nsContentUtils.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIEventTarget.h"
#include "nsILoadInfo.h"
#include "nsTObserverArray.h"
#include "js/ContextOptions.h"
@ -712,7 +717,7 @@ class WorkerPrivate : public RelativeTimeline {
return mLoadInfo.mChannel.forget();
}
nsPIDOMWindowInner* GetWindow() {
nsPIDOMWindowInner* GetWindow() const {
AssertIsOnMainThread();
return mLoadInfo.mWindow;
}
@ -902,6 +907,34 @@ class WorkerPrivate : public RelativeTimeline {
return mAgentClusterOpenerPolicy;
}
/**
* COEP Methods
*
* If browser.tabs.remote.useCrossOriginEmbedderPolicy=false, these methods
* will, depending on the return type, return a value that will avoid
* assertion failures or a value that won't block loads.
*/
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> GetEmbedderPolicy() const;
// Fails if a policy has already been set or if `aPolicy` violates the owner's
// policy, if an owner exists.
mozilla::Result<Ok, nsresult> SetEmbedderPolicy(
nsILoadInfo::CrossOriginEmbedderPolicy aPolicy);
// `aRequest` is the request loading the worker and must be QI-able to
// `nsIChannel*`. It's used to verify that the worker can indeed inherit its
// owner's COEP (when an owner exists).
//
// TODO: remove `aRequest`; currently, it's required because instances may not
// always know its final, resolved script URL or have access internally to
// `aRequest`.
void InheritOwnerEmbedderPolicyOrNull(nsIRequest* aRequest);
// Requires a policy to already have been set.
bool MatchEmbedderPolicy(
nsILoadInfo::CrossOriginEmbedderPolicy aPolicy) const;
private:
WorkerPrivate(
WorkerPrivate* aParent, const nsAString& aScriptURL, bool aIsChromeWorker,
@ -997,6 +1030,8 @@ class WorkerPrivate : public RelativeTimeline {
// executed.
void DispatchCancelingRunnable();
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> GetOwnerEmbedderPolicy() const;
class EventTarget;
friend class EventTarget;
friend class AutoSyncLoopHolder;
@ -1011,9 +1046,9 @@ class WorkerPrivate : public RelativeTimeline {
SharedMutex mMutex;
mozilla::CondVar mCondVar;
WorkerPrivate* mParent;
WorkerPrivate* const mParent;
nsString mScriptURL;
const nsString mScriptURL;
// This is the worker name for shared workers and dedicated workers.
nsString mWorkerName;
@ -1220,6 +1255,14 @@ class WorkerPrivate : public RelativeTimeline {
// This is used to check if it's allowed to share the memory across the agent
// cluster.
const nsILoadInfo::CrossOriginOpenerPolicy mAgentClusterOpenerPolicy;
// Member variable of this class rather than the worker global scope because
// it's received on the main thread, but the global scope is thread-bound
// to the worker thread, so storing the value in the global scope would
// involve sacrificing the thread-bound-ness or using a WorkerRunnable, and
// there isn't a strong reason to store it on the global scope other than
// better consistency with the COEP spec.
Maybe<nsILoadInfo::CrossOriginEmbedderPolicy> mEmbedderPolicy;
};
class AutoSyncLoopHolder {