gecko-dev/dom/security/CSPEvalChecker.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

192 строки
5.9 KiB
C++
Исходник Обычный вид История

/* -*- 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 "mozilla/dom/CSPEvalChecker.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "nsIParentChannel.h"
#include "nsGlobalWindowInner.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsCOMPtr.h"
#include "nsJSUtils.h"
using namespace mozilla;
using namespace mozilla::dom;
namespace {
// We use the subjectPrincipal to assert that eval() is never
// executed in system privileged context.
nsresult CheckInternal(nsIContentSecurityPolicy* aCSP,
nsICSPEventListener* aCSPEventListener,
nsIPrincipal* aSubjectPrincipal,
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 !defined(ANDROID)
JSContext* cx = nsContentUtils::GetCurrentJSContext();
Bug 1583949 - Add a check for IsEvalAllowed to the worker callpath for eval() r=ckerschb,baku This patch does several things. Because Workers aren't on the main thread, many of the things done are in the name of off main thread access. 1) Changes a parameter in IsEvalAllowed from a nsIPrincipal to a bool. We only used the principal to determined if it was the System Principal. Principals aren't thread safe and can only be accessed on Main Thread, so if we passed a Principal in, we would be in error. Instead only pass in the bool which - for workers - comes from a thread-safe location. 2) Separates out the Telemetry Event Recording and sending a message to the console into a new function nsContentSecurityUtils::NotifyEvalUsage. (And creates a runnable that calls it.) We do this because we will need to only call this method on the main thread. Telemetry Event Recording has only ever been called on the Main Thread. While I possibly-successfully cut it over to happen Off Main Thread (OMT) by porting preferences to StaticPrefs, I don't know if there were other threading assumptions in the Telemetry Code. So it would be much safer to just continue recording Event Telemetry on the main thread. Sending a message to the console requires calling GetStringBundleService() which requires main thread. I didn't investigate if this could be made thread-safe, I just threw it onto the main thread too. If, in IsEvalAllowed, we are on the main thread - we call NotifyEvalUsage directly. If we are not, we create a runnable which will then call NotifyEvalUsage for us on the main thread. 3) Ports allow_eval_with_system_principal and allow_eval_in_parent_process from bools to RelaxedAtomicBool - because we now check these prefs OMT. 4) In RuntimeService.cpp, adds the call to IsEvalAllowed. 5) Add resource://gre/modules/workers/require.js to the allowlist of eval usage. This was the script that identified this gap in the first place. It uses eval (twice) for structural reasons (scope and line number massaging.) The contents of the eval are the result of a request to a uri (which may be internal, like resource://). The whole point of this is to implement a CommonJS require() api. This usage of eval is safe because the only way an attacker can inject into it is by either controlling the response of the uri request or controlling (or appending to) the argument. If they can do that, they are able to inject script into Firefox even if we cut this usage of eval over to some other type of safe(r) script loader. Bug 1584564 tracks making sure calls to require.js are safe. 6) Adds cld-worker.js to the allowlist. Bug 1584605 is for refactoring that eval usage, which is decidedly non-trivial. 7) Does _not_ enforce the eval restrictions for workers. While I've gotten try to be green and not throw up any instances of eval-usage by workers, it is much safer to deploy this is Telemetry-only mode for Workers for a little bit to see if anything pops up from the Nightly population. Bug 1584602 is for enforcing the checks. Differential Revision: https://phabricator.services.mozilla.com/D47480 --HG-- extra : moz-landing-system : lando
2019-10-08 20:31:35 +03:00
if (!nsContentSecurityUtils::IsEvalAllowed(
cx, aSubjectPrincipal->IsSystemPrincipal(), aExpression)) {
*aAllowed = false;
return NS_OK;
}
#endif
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,
nullptr, // triggering element
aCSPEventListener, aFileNameString, aExpression,
aLineNum, aColumnNum, 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(), mWorkerPrivate->CSPEventListener(),
mWorkerPrivate->GetLoadingPrincipal(), 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;
};
} // namespace
/* static */
nsresult CSPEvalChecker::CheckForWindow(JSContext* aCx,
nsGlobalWindowInner* 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<Document> doc = aWindow->GetExtantDoc();
if (!doc) {
// if there's no document, we don't have to do anything.
*aAllowEval = true;
return NS_OK;
}
nsresult rv = NS_OK;
// Get the calling location.
uint32_t lineNum = 0;
uint32_t columnNum = 0;
nsAutoString fileNameString;
if (!nsJSUtils::GetCallingLocation(aCx, fileNameString, &lineNum,
&columnNum)) {
fileNameString.AssignLiteral("unknown");
}
nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();
rv = CheckInternal(csp, nullptr /* no CSPEventListener for window */,
doc->NodePrincipal(), 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<WorkerCSPCheckRunnable> 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;
}