diff --git a/dom/base/nsJSTimeoutHandler.cpp b/dom/base/nsJSTimeoutHandler.cpp index 5c836694ae..6e8c6155c4 100644 --- a/dom/base/nsJSTimeoutHandler.cpp +++ b/dom/base/nsJSTimeoutHandler.cpp @@ -9,6 +9,7 @@ #include "mozilla/Function.h" #include "mozilla/Likely.h" #include "mozilla/Maybe.h" +#include "mozilla/dom/CSPEvalChecker.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/ModuleScript.h" #include "nsAXPCNativeCallContext.h" @@ -52,7 +53,8 @@ public: Function& aFunction, nsTArray>&& aArguments); nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const nsAString& aExpression); + const nsAString& aExpression, bool* aAllowEval, + ErrorResult& aRv); virtual const nsAString& GetHandlerText() override; @@ -182,54 +184,6 @@ NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsJSScriptTimeoutHandler) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsJSScriptTimeoutHandler) -static bool -CheckCSPForEval(JSContext* aCx, nsGlobalWindow* aWindow, ErrorResult& aError) -{ - // if CSP is enabled, and setTimeout/setInterval was called with a string, - // disable the registration and log an error - nsCOMPtr doc = aWindow->GetExtantDoc(); - if (!doc) { - // if there's no document, we don't have to do anything. - return true; - } - - nsCOMPtr csp; - aError = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); - if (aError.Failed()) { - return false; - } - - if (!csp) { - return true; - } - - bool allowsEval = true; - bool reportViolation = false; - aError = csp->GetAllowsEval(&reportViolation, &allowsEval); - if (aError.Failed()) { - return false; - } - - if (reportViolation) { - // TODO : need actual script sample in violation report. - NS_NAMED_LITERAL_STRING(scriptSample, - "call to eval() or related function blocked by CSP"); - - // Get the calling location. - uint32_t lineNum = 0; - nsAutoString fileNameString; - if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum)) { - fileNameString.AssignLiteral("unknown"); - } - - csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, - fileNameString, scriptSample, lineNum, - EmptyString(), EmptyString()); - } - - return allowsEval; -} - nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler() : mLineNo(0) , mColumn(0) @@ -273,8 +227,9 @@ nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, return; } - *aAllowEval = CheckCSPForEval(aCx, aWindow, aError); - if (aError.Failed() || !*aAllowEval) { + aError = CSPEvalChecker::CheckForWindow(aCx, aWindow, aExpression, + aAllowEval); + if (NS_WARN_IF(aError.Failed()) || !*aAllowEval) { return; } @@ -297,7 +252,9 @@ nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const nsAString& aExpression) + const nsAString& aExpression, + bool* aAllowEval, + ErrorResult& aError) : mLineNo(0) , mColumn(0) , mExpr(aExpression) @@ -305,6 +262,12 @@ nsJSScriptTimeoutHandler::nsJSScriptTimeoutHandler(JSContext* aCx, MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); + aError = CSPEvalChecker::CheckForWorker(aCx, aWorkerPrivate, aExpression, + aAllowEval); + if (NS_WARN_IF(aError.Failed()) || !*aAllowEval) { + return; + } + Init(aCx); } @@ -399,9 +362,15 @@ NS_CreateJSTimeoutHandler(JSContext *aCx, WorkerPrivate* aWorkerPrivate, already_AddRefed NS_CreateJSTimeoutHandler(JSContext* aCx, WorkerPrivate* aWorkerPrivate, - const nsAString& aExpression) + const nsAString& aExpression, ErrorResult& aRv) { + bool allowEval = false; RefPtr handler = - new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aExpression); + new nsJSScriptTimeoutHandler(aCx, aWorkerPrivate, aExpression, &allowEval, + aRv); + if (aRv.Failed() || !allowEval) { + return nullptr; + } + return handler.forget(); } diff --git a/dom/security/CSPEvalChecker.cpp b/dom/security/CSPEvalChecker.cpp new file mode 100644 index 0000000000..42b5610c7d --- /dev/null +++ b/dom/security/CSPEvalChecker.cpp @@ -0,0 +1,183 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/CSPEvalChecker.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/dom/WorkerRunnable.h" +#include "mozilla/ErrorResult.h" +#include "nsGlobalWindow.h" +#include "nsIDocument.h" +#include "nsCOMPtr.h" +#include "nsJSUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +nsresult +CheckInternal(nsIContentSecurityPolicy* aCSP, + const nsAString& aExpression, + const nsAString& aFileNameString, + uint32_t aLineNum, + uint32_t aColumnNum, + bool* aAllowed) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aAllowed); + + // The value is set at any "return", but better to have a default value here. + *aAllowed = false; + + if (!aCSP) { + *aAllowed = true; + return NS_OK; + } + + bool reportViolation = false; + nsresult rv = aCSP->GetAllowsEval(&reportViolation, aAllowed); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aAllowed = false; + return rv; + } + + if (reportViolation) { + aCSP->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, + aFileNameString, aExpression, aLineNum, + EmptyString(), EmptyString()); + } + + return NS_OK; +} + +class WorkerCSPCheckRunnable final : public WorkerMainThreadRunnable +{ +public: + WorkerCSPCheckRunnable(WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression, + const nsAString& aFileNameString, + uint32_t aLineNum, + uint32_t aColumnNum) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("CSP Eval Check")) + , mExpression(aExpression) + , mFileNameString(aFileNameString) + , mLineNum(aLineNum) + , mColumnNum(aColumnNum) + , mEvalAllowed(false) + {} + + bool + MainThreadRun() override + { + mResult = CheckInternal(mWorkerPrivate->GetCSP(), mExpression, + mFileNameString, mLineNum, mColumnNum, + &mEvalAllowed); + return true; + } + + nsresult + GetResult(bool* aAllowed) + { + MOZ_ASSERT(aAllowed); + *aAllowed = mEvalAllowed; + return mResult; + } + +private: + const nsString mExpression; + const nsString mFileNameString; + const uint32_t mLineNum; + const uint32_t mColumnNum; + bool mEvalAllowed; + nsresult mResult; +}; + +} // anonymous + +/* static */ nsresult +CSPEvalChecker::CheckForWindow(JSContext* aCx, nsGlobalWindow* aWindow, + const nsAString& aExpression, bool* aAllowEval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aWindow); + MOZ_ASSERT(aAllowEval); + + // The value is set at any "return", but better to have a default value here. + *aAllowEval = false; + + // if CSP is enabled, and setTimeout/setInterval was called with a string, + // disable the registration and log an error + nsCOMPtr doc = aWindow->GetExtantDoc(); + if (!doc) { + // if there's no document, we don't have to do anything. + *aAllowEval = true; + return NS_OK; + } + + nsCOMPtr csp; + nsresult rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aAllowEval = false; + return rv; + } + + // Get the calling location. + uint32_t lineNum = 0; + uint32_t columnNum = 0; + nsAutoString fileNameString; + if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum, + &columnNum)) { + fileNameString.AssignLiteral("unknown"); + } + + rv = CheckInternal(csp, aExpression, fileNameString, lineNum, columnNum, + aAllowEval); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aAllowEval = false; + return rv; + } + + return NS_OK; +} + +/* static */ nsresult +CSPEvalChecker::CheckForWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression, bool* aAllowEval) +{ + MOZ_ASSERT(aWorkerPrivate); + aWorkerPrivate->AssertIsOnWorkerThread(); + MOZ_ASSERT(aAllowEval); + + // The value is set at any "return", but better to have a default value here. + *aAllowEval = false; + + // Get the calling location. + uint32_t lineNum = 0; + uint32_t columnNum = 0; + nsAutoString fileNameString; + if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum, + &columnNum)) { + fileNameString.AssignLiteral("unknown"); + } + + RefPtr r = + new WorkerCSPCheckRunnable(aWorkerPrivate, aExpression, fileNameString, + lineNum, columnNum); + ErrorResult error; + r->Dispatch(Canceling, error); + if (NS_WARN_IF(error.Failed())) { + *aAllowEval = false; + return error.StealNSResult(); + } + + nsresult rv = r->GetResult(aAllowEval); + if (NS_WARN_IF(NS_FAILED(rv))) { + *aAllowEval = false; + return rv; + } + + return NS_OK; +} diff --git a/dom/security/CSPEvalChecker.h b/dom/security/CSPEvalChecker.h new file mode 100644 index 0000000000..5a73d7652f --- /dev/null +++ b/dom/security/CSPEvalChecker.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_CSPEvalChecker_h +#define mozilla_dom_CSPEvalChecker_h + +#include "nsString.h" + +struct JSContext; +class nsGlobalWindow; + +namespace mozilla { +namespace dom { +namespace workers { +class WorkerPrivate; +} + +using namespace mozilla::dom::workers; + +class CSPEvalChecker final +{ +public: + static nsresult + CheckForWindow(JSContext* aCx, nsGlobalWindow* aWindow, + const nsAString& aExpression, bool* aAllowEval); + + static nsresult + CheckForWorker(JSContext* aCx, WorkerPrivate* aWorkerPrivate, + const nsAString& aExpression, bool* aAllowEval); +}; + +} // dom namespace +} // mozilla namespace + +#endif // mozilla_dom_CSPEvalChecker_h diff --git a/dom/security/moz.build b/dom/security/moz.build index 3f690ea498..224b7a4a9a 100644 --- a/dom/security/moz.build +++ b/dom/security/moz.build @@ -7,6 +7,7 @@ TEST_DIRS += ['test'] EXPORTS.mozilla.dom += [ 'ContentVerifier.h', + 'CSPEvalChecker.h', 'nsContentSecurityManager.h', 'nsCSPContext.h', 'nsCSPService.h', @@ -24,6 +25,7 @@ EXPORTS += [ UNIFIED_SOURCES += [ 'ContentVerifier.cpp', + 'CSPEvalChecker.cpp', 'nsContentSecurityManager.cpp', 'nsCSPContext.cpp', 'nsCSPParser.cpp', @@ -39,6 +41,7 @@ include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/caps', + '/dom/base', '/netwerk/base', ] diff --git a/dom/workers/Queue.h b/dom/workers/Queue.h index aa673f587e..b6513acdb7 100644 --- a/dom/workers/Queue.h +++ b/dom/workers/Queue.h @@ -6,7 +6,7 @@ #ifndef mozilla_dom_workers_queue_h__ #define mozilla_dom_workers_queue_h__ -#include "Workers.h" +#include "mozilla/dom/workers/Workers.h" #include "mozilla/Mutex.h" #include "nsTArray.h" diff --git a/dom/workers/ScriptLoader.cpp b/dom/workers/ScriptLoader.cpp index 16f0dd1527..646182bfd3 100644 --- a/dom/workers/ScriptLoader.cpp +++ b/dom/workers/ScriptLoader.cpp @@ -1163,7 +1163,7 @@ private: ("Scriptloader::Load, SRI required but not supported in workers")); nsCOMPtr wcsp; chanLoadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(wcsp)); - MOZ_ASSERT(wcsp, "We sould have a CSP for the worker here"); + MOZ_ASSERT(wcsp, "We should have a CSP for the worker here"); if (wcsp) { wcsp->LogViolationDetails( nsIContentSecurityPolicy::VIOLATION_TYPE_REQUIRE_SRI_FOR_SCRIPT, diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index dda81beb57..0ca8766438 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -6,7 +6,7 @@ #ifndef mozilla_dom_workers_workerprivate_h__ #define mozilla_dom_workers_workerprivate_h__ -#include "Workers.h" +#include "mozilla/dom/workers/Workers.h" #include "js/CharacterEncoding.h" #include "nsIContentPolicy.h" @@ -34,8 +34,8 @@ #include "nsThreadUtils.h" #include "nsTObserverArray.h" -#include "Queue.h" -#include "WorkerHolder.h" +#include "mozilla/dom/workerinternals/Queue.h" +#include "mozilla/dom/workers/bindings/WorkerHolder.h" #ifdef XP_WIN #undef PostMessage diff --git a/dom/workers/WorkerRunnable.h b/dom/workers/WorkerRunnable.h index 7484ba6855..05fe31df8b 100644 --- a/dom/workers/WorkerRunnable.h +++ b/dom/workers/WorkerRunnable.h @@ -6,14 +6,14 @@ #ifndef mozilla_dom_workers_workerrunnable_h__ #define mozilla_dom_workers_workerrunnable_h__ -#include "Workers.h" +#include "mozilla/dom/workers/Workers.h" #include "nsICancelableRunnable.h" #include "mozilla/Atomics.h" #include "nsISupportsImpl.h" #include "nsThreadUtils.h" /* nsRunnable */ -#include "WorkerHolder.h" +#include "mozilla/dom/workers/bindings/WorkerHolder.h" struct JSContext; class nsIEventTarget; diff --git a/dom/workers/WorkerScope.cpp b/dom/workers/WorkerScope.cpp index df75ff887a..ef6abc25e2 100644 --- a/dom/workers/WorkerScope.cpp +++ b/dom/workers/WorkerScope.cpp @@ -58,7 +58,8 @@ NS_CreateJSTimeoutHandler(JSContext* aCx, extern already_AddRefed NS_CreateJSTimeoutHandler(JSContext* aCx, mozilla::dom::workers::WorkerPrivate* aWorkerPrivate, - const nsAString& aExpression); + const nsAString& aExpression, + mozilla::ErrorResult& aRv); using namespace mozilla; using namespace mozilla::dom; @@ -268,7 +269,7 @@ WorkerGlobalScope::SetTimeout(JSContext* aCx, nsCOMPtr handler = NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aArguments, aRv); - if (NS_WARN_IF(aRv.Failed())) { + if (!handler) { return 0; } @@ -285,7 +286,11 @@ WorkerGlobalScope::SetTimeout(JSContext* aCx, mWorkerPrivate->AssertIsOnWorkerThread(); nsCOMPtr handler = - NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler); + NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aRv); + if (!handler) { + return 0; + } + return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, false, aRv); } @@ -326,7 +331,10 @@ WorkerGlobalScope::SetInterval(JSContext* aCx, Sequence dummy; nsCOMPtr handler = - NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler); + NS_CreateJSTimeoutHandler(aCx, mWorkerPrivate, aHandler, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return 0; + } return mWorkerPrivate->SetTimeout(aCx, handler, aTimeout, true, aRv); } diff --git a/dom/workers/moz.build b/dom/workers/moz.build index 5421d65a8d..e3fe845f9e 100644 --- a/dom/workers/moz.build +++ b/dom/workers/moz.build @@ -19,6 +19,11 @@ EXPORTS.mozilla.dom += [ 'WorkerScope.h', ] +# Private stuff. +EXPORTS.mozilla.dom.workerinternals += [ + 'Queue.h', +] + EXPORTS.mozilla.dom.workers += [ 'RuntimeService.h', 'ServiceWorkerCommon.h',