Bug 1905239 - Add new parameters to HostEnsureCanCompileStrings hook. r=tschuster
Currently, we do this via isRuntimeCodeGenEnabled whose single argument is equivalent to codeString in "Dynamic Code Brand Checks" spec [1]. We extend this hook to accept new parameters from that spec and adjust PerformEval and CreateDynamicFunction accordingly. We don't change the behavior for PerformShadowRealmEval [2] and WASM, i.e. we keep dummy parameters. [1] https://tc39.es/proposal-dynamic-code-brand-checks [2] https://github.com/tc39/proposal-shadowrealm/issues/414 Differential Revision: https://phabricator.services.mozilla.com/D229588
This commit is contained in:
@@ -73,6 +73,8 @@
|
|||||||
#include "nsJSUtils.h"
|
#include "nsJSUtils.h"
|
||||||
#include "nsILoadInfo.h"
|
#include "nsILoadInfo.h"
|
||||||
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
|
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
|
||||||
|
#include "js/GCVector.h"
|
||||||
|
#include "js/Value.h"
|
||||||
|
|
||||||
// This should be probably defined on some other place... but I couldn't find it
|
// This should be probably defined on some other place... but I couldn't find it
|
||||||
#define WEBAPPS_PERM_NAME "webapps-manage"
|
#define WEBAPPS_PERM_NAME "webapps-manage"
|
||||||
@@ -462,7 +464,12 @@ NS_IMPL_ISUPPORTS(nsScriptSecurityManager, nsIScriptSecurityManager)
|
|||||||
///////////////// Security Checks /////////////////
|
///////////////// Security Checks /////////////////
|
||||||
|
|
||||||
bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
||||||
JSContext* cx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCode) {
|
JSContext* cx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
|
||||||
|
JS::CompilationType aCompilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
|
||||||
|
JS::Handle<JSString*> aBodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
|
||||||
|
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) {
|
||||||
MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
|
MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
|
||||||
|
|
||||||
nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
|
nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
|
||||||
@@ -477,13 +484,14 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
|||||||
if (contextForbidsEval) {
|
if (contextForbidsEval) {
|
||||||
nsAutoJSString scriptSample;
|
nsAutoJSString scriptSample;
|
||||||
if (aKind == JS::RuntimeCode::JS &&
|
if (aKind == JS::RuntimeCode::JS &&
|
||||||
NS_WARN_IF(!scriptSample.init(cx, aCode))) {
|
NS_WARN_IF(!scriptSample.init(cx, aCodeString))) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsContentSecurityUtils::IsEvalAllowed(
|
if (!nsContentSecurityUtils::IsEvalAllowed(
|
||||||
cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
|
cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
|
||||||
return false;
|
*aOutCanCompileStrings = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,6 +511,7 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
|||||||
}
|
}
|
||||||
// don't do anything unless there's a CSP
|
// don't do anything unless there's a CSP
|
||||||
if (!csp) {
|
if (!csp) {
|
||||||
|
*aOutCanCompileStrings = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -522,7 +531,8 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
|||||||
nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK);
|
nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
NS_WARNING("CSP: failed to get allowsEval");
|
NS_WARNING("CSP: failed to get allowsEval");
|
||||||
return true; // fail open to not break sites.
|
*aOutCanCompileStrings = true; // fail open to not break sites.
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (NS_FAILED(csp->GetAllowsWasmEval(&reportViolation, &evalOK))) {
|
if (NS_FAILED(csp->GetAllowsWasmEval(&reportViolation, &evalOK))) {
|
||||||
@@ -545,8 +555,7 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
|||||||
auto caller = JSCallingLocation::Get(cx);
|
auto caller = JSCallingLocation::Get(cx);
|
||||||
nsAutoJSString scriptSample;
|
nsAutoJSString scriptSample;
|
||||||
if (aKind == JS::RuntimeCode::JS &&
|
if (aKind == JS::RuntimeCode::JS &&
|
||||||
NS_WARN_IF(!scriptSample.init(cx, aCode))) {
|
NS_WARN_IF(!scriptSample.init(cx, aCodeString))) {
|
||||||
JS_ClearPendingException(cx);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
uint16_t violationType =
|
uint16_t violationType =
|
||||||
@@ -559,7 +568,8 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
|||||||
caller.mLine, caller.mColumn, u""_ns, u""_ns);
|
caller.mLine, caller.mColumn, u""_ns, u""_ns);
|
||||||
}
|
}
|
||||||
|
|
||||||
return evalOK;
|
*aOutCanCompileStrings = evalOK;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class SystemPrincipal;
|
|||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
enum class RuntimeCode;
|
enum class RuntimeCode;
|
||||||
|
enum class CompilationType;
|
||||||
} // namespace JS
|
} // namespace JS
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
@@ -91,9 +92,13 @@ class nsScriptSecurityManager final : public nsIScriptSecurityManager {
|
|||||||
virtual ~nsScriptSecurityManager();
|
virtual ~nsScriptSecurityManager();
|
||||||
|
|
||||||
// Decides, based on CSP, whether or not eval() and stuff can be executed.
|
// Decides, based on CSP, whether or not eval() and stuff can be executed.
|
||||||
static bool ContentSecurityPolicyPermitsJSAction(JSContext* cx,
|
static bool ContentSecurityPolicyPermitsJSAction(
|
||||||
JS::RuntimeCode kind,
|
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
|
||||||
JS::Handle<JSString*> aCode);
|
JS::CompilationType aCompilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
|
||||||
|
JS::Handle<JSString*> aBodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
|
||||||
|
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings);
|
||||||
|
|
||||||
static bool JSPrincipalsSubsume(JSPrincipals* first, JSPrincipals* second);
|
static bool JSPrincipalsSubsume(JSPrincipals* first, JSPrincipals* second);
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,10 @@
|
|||||||
#include "jsfriendapi.h"
|
#include "jsfriendapi.h"
|
||||||
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
#include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_*
|
||||||
#include "js/ContextOptions.h"
|
#include "js/ContextOptions.h"
|
||||||
|
#include "js/GCVector.h"
|
||||||
#include "js/Initialization.h"
|
#include "js/Initialization.h"
|
||||||
#include "js/LocaleSensitive.h"
|
#include "js/LocaleSensitive.h"
|
||||||
|
#include "js/Value.h"
|
||||||
#include "js/WasmFeatures.h"
|
#include "js/WasmFeatures.h"
|
||||||
#include "mozilla/ArrayUtils.h"
|
#include "mozilla/ArrayUtils.h"
|
||||||
#include "mozilla/Atomics.h"
|
#include "mozilla/Atomics.h"
|
||||||
@@ -499,8 +501,13 @@ class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable {
|
|||||||
~LogViolationDetailsRunnable() = default;
|
~LogViolationDetailsRunnable() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind,
|
bool ContentSecurityPolicyAllows(
|
||||||
JS::Handle<JSString*> aCode) {
|
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
|
||||||
|
JS::CompilationType aCompilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
|
||||||
|
JS::Handle<JSString*> aBodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
|
||||||
|
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) {
|
||||||
WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
|
WorkerPrivate* worker = GetWorkerPrivateFromContext(aCx);
|
||||||
worker->AssertIsOnWorkerThread();
|
worker->AssertIsOnWorkerThread();
|
||||||
|
|
||||||
@@ -509,14 +516,14 @@ bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind,
|
|||||||
uint16_t violationType;
|
uint16_t violationType;
|
||||||
nsAutoJSString scriptSample;
|
nsAutoJSString scriptSample;
|
||||||
if (aKind == JS::RuntimeCode::JS) {
|
if (aKind == JS::RuntimeCode::JS) {
|
||||||
if (NS_WARN_IF(!scriptSample.init(aCx, aCode))) {
|
if (NS_WARN_IF(!scriptSample.init(aCx, aCodeString))) {
|
||||||
JS_ClearPendingException(aCx);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!nsContentSecurityUtils::IsEvalAllowed(
|
if (!nsContentSecurityUtils::IsEvalAllowed(
|
||||||
aCx, worker->UsesSystemPrincipal(), scriptSample)) {
|
aCx, worker->UsesSystemPrincipal(), scriptSample)) {
|
||||||
return false;
|
*aOutCanCompileStrings = false;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
evalOK = worker->IsEvalAllowed();
|
evalOK = worker->IsEvalAllowed();
|
||||||
@@ -542,7 +549,8 @@ bool ContentSecurityPolicyAllows(JSContext* aCx, JS::RuntimeCode aKind,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return evalOK;
|
*aOutCanCompileStrings = evalOK;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) {
|
void CTypesActivityCallback(JSContext* aCx, JS::CTypesActivityType aType) {
|
||||||
|
|||||||
@@ -77,20 +77,35 @@ typedef bool (*JSSubsumesOp)(JSPrincipals* first, JSPrincipals* second);
|
|||||||
|
|
||||||
namespace JS {
|
namespace JS {
|
||||||
enum class RuntimeCode { JS, WASM };
|
enum class RuntimeCode { JS, WASM };
|
||||||
|
enum class CompilationType { DirectEval, IndirectEval, Function, Undefined };
|
||||||
} // namespace JS
|
} // namespace JS
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Used to check if a CSP instance wants to disable eval() and friends.
|
* Used to check if a CSP instance wants to disable eval() and friends.
|
||||||
* See JSContext::isRuntimeCodeGenEnabled() in vm/JSContext.cpp.
|
* See JSContext::isRuntimeCodeGenEnabled() in vm/JSContext.cpp.
|
||||||
*
|
*
|
||||||
* `code` is the JavaScript source code passed to eval/Function, but nullptr
|
* codeString, compilationType, parameterStrings, bodyString, parameterArgs,
|
||||||
* for Wasm.
|
* and bodyArg are defined in the "Dynamic Code Brand Checks" spec
|
||||||
|
* (see https://tc39.es/proposal-dynamic-code-brand-checks).
|
||||||
*
|
*
|
||||||
* Returning `false` from this callback will prevent the execution/compilation
|
* An Undefined compilationType is used for cases that are not covered by that
|
||||||
* of the code.
|
* spec and unused parameters are null/empty. Currently, this includes Wasm
|
||||||
|
* (only check if compilation is enabled) and ShadowRealmEval (only check
|
||||||
|
* codeString).
|
||||||
|
*
|
||||||
|
* `outCanCompileStrings` is set to false if this callback prevents the
|
||||||
|
* execution/compilation of the code and to true otherwise.
|
||||||
|
*
|
||||||
|
* Return false on failure, true on success. The |outCanCompileStrings|
|
||||||
|
* parameter should not be modified in case of failure.
|
||||||
*/
|
*/
|
||||||
typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::RuntimeCode kind,
|
typedef bool (*JSCSPEvalChecker)(
|
||||||
JS::HandleString code);
|
JSContext* cx, JS::RuntimeCode kind, JS::Handle<JSString*> codeString,
|
||||||
|
JS::CompilationType compilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> parameterStrings,
|
||||||
|
JS::Handle<JSString*> bodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> parameterArgs,
|
||||||
|
JS::Handle<JS::Value> bodyArg, bool* outCanCompileStrings);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Provide a string of code from an Object argument, to be used by eval.
|
* Provide a string of code from an Object argument, to be used by eval.
|
||||||
|
|||||||
@@ -257,7 +257,17 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Steps 6-8.
|
// Steps 6-8.
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(
|
||||||
|
JS::RuntimeCode::JS, str,
|
||||||
|
evalType == DIRECT_EVAL ? JS::CompilationType::DirectEval
|
||||||
|
: JS::CompilationType::IndirectEval,
|
||||||
|
parameterStrings, str, parameterArgs, v, &canCompileStrings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_EVAL);
|
JSMSG_CSP_BLOCKED_EVAL);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -196,7 +196,16 @@ static bool PerformShadowRealmEval(JSContext* cx, Handle<JSString*> sourceText,
|
|||||||
MOZ_ASSERT(callerRealm != evalRealm);
|
MOZ_ASSERT(callerRealm != evalRealm);
|
||||||
|
|
||||||
// Step 1. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm).
|
// Step 1. Perform ? HostEnsureCanCompileStrings(callerRealm, evalRealm).
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, sourceText)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, sourceText,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_SHADOWREALM);
|
JSMSG_CSP_BLOCKED_SHADOWREALM);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -92,3 +92,184 @@ BEGIN_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
END_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval)
|
END_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval)
|
||||||
|
|
||||||
|
// This snippet defines a TrustedType that wraps some trustedCode string and
|
||||||
|
// stringifies to that string, as well as a helper to create a fake instance
|
||||||
|
// that can stringify to a different string.
|
||||||
|
const char* customTypesSnippet =
|
||||||
|
"function TrustedType(aTrustedCode) { this.trustedCode = aTrustedCode; };"
|
||||||
|
"TrustedType.prototype.toString = function() { return this.trustedCode; };"
|
||||||
|
"function CreateFakeTrustedType(aTrustedCode, aString) {"
|
||||||
|
" let fake = new TrustedType(aTrustedCode);"
|
||||||
|
" fake.toString = () => { return aString; };"
|
||||||
|
" return fake;"
|
||||||
|
"};";
|
||||||
|
|
||||||
|
BEGIN_TEST(testDynamicCodeBrandChecks_CustomHostEnsureCanCompileStrings) {
|
||||||
|
JSSecurityCallbacks securityCallbacksWithCustomHostEnsureCanCompileStrings = {
|
||||||
|
StringifiedObjectsMatchTrustedCodeProperties, // contentSecurityPolicyAllows
|
||||||
|
ExtractTrustedCodeStringProperty, // codeForEvalGets
|
||||||
|
nullptr // subsumes
|
||||||
|
};
|
||||||
|
JS_SetSecurityCallbacks(
|
||||||
|
cx, &securityCallbacksWithCustomHostEnsureCanCompileStrings);
|
||||||
|
JS::RootedValue v(cx);
|
||||||
|
|
||||||
|
EXEC(customTypesSnippet);
|
||||||
|
|
||||||
|
// String arguments are evaluated.
|
||||||
|
EVAL("eval('5*8');", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 40);
|
||||||
|
EVAL("(new Function('a', 'b', 'return a * b'))(6, 7);", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 42);
|
||||||
|
|
||||||
|
// The same works with TrustedType wrappers.
|
||||||
|
EVAL("eval(new TrustedType('5*8'));", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 40);
|
||||||
|
EVAL(
|
||||||
|
"(new Function(new TrustedType('a'), new TrustedType('b'), new "
|
||||||
|
"TrustedType('return a * b')))(6, 7);",
|
||||||
|
&v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 42);
|
||||||
|
|
||||||
|
// new Function fails if one of the stringified argument does not match the
|
||||||
|
// trustedCode property.
|
||||||
|
CHECK(!execDontReport(
|
||||||
|
"new Function(CreateFakeTrustedType('a', 'c'), 'b', 'return b');",
|
||||||
|
__FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
CHECK(!execDontReport(
|
||||||
|
"new Function('a', CreateFakeTrustedType('b', 'c'), 'return a');",
|
||||||
|
__FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
CHECK(
|
||||||
|
!execDontReport("new Function('a', 'b', CreateFakeTrustedType('return a "
|
||||||
|
"* b', 'return a + b'));",
|
||||||
|
__FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
// new Function also fails if StringifiedObjectsMatchTrustedCodeProperties
|
||||||
|
// returns false.
|
||||||
|
CHECK(!execDontReport("new Function('a', 'b', new TrustedType(undefined));",
|
||||||
|
__FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
// PerformEval relies on ExtractTrustedCodeProperty rather than toString() to
|
||||||
|
// obtain the code to execute, so StringifiedObjectsMatchTrustedCodeProperties
|
||||||
|
// will always allow the code execution for the specified security callbacks.
|
||||||
|
EVAL("eval(CreateFakeTrustedType('5*8', '6*7'));", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 40);
|
||||||
|
EVAL("eval(new TrustedType(undefined));", &v);
|
||||||
|
CHECK(v.isObject());
|
||||||
|
JS::RootedObject obj(cx, &v.toObject());
|
||||||
|
JS::RootedValue trustedCode(cx);
|
||||||
|
CHECK(JS_GetProperty(cx, obj, "trustedCode", &trustedCode));
|
||||||
|
CHECK(trustedCode.isUndefined());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a HostEnsureCanCompileStrings() implementation similar to some checks
|
||||||
|
// described in the CSP spec: verify that aBodyString and aParameterStrings
|
||||||
|
// match the corresponding trustedCode property on aBodyArg and aParameterArgs
|
||||||
|
// objects. See https://w3c.github.io/webappsec-csp/#can-compile-strings
|
||||||
|
static bool StringifiedObjectsMatchTrustedCodeProperties(
|
||||||
|
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
|
||||||
|
JS::CompilationType aCompilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
|
||||||
|
JS::Handle<JSString*> aBodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
|
||||||
|
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) {
|
||||||
|
bool isTrusted = true;
|
||||||
|
auto comparePropertyAndString = [&aCx, &isTrusted](
|
||||||
|
JS::Handle<JS::Value> aValue,
|
||||||
|
JS::Handle<JSString*> aString) {
|
||||||
|
if (!aValue.isObject()) {
|
||||||
|
// Just trust non-Objects.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
JS::RootedObject obj(aCx, &aValue.toObject());
|
||||||
|
|
||||||
|
JS::RootedString trustedCode(aCx);
|
||||||
|
if (!ExtractTrustedCodeStringProperty(aCx, obj, &trustedCode)) {
|
||||||
|
// Propagate the failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!trustedCode) {
|
||||||
|
// Emulate a failure if trustedCode is undefined.
|
||||||
|
JS_ReportErrorASCII(aCx, "test failed, trustedCode property is undefined");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool equals;
|
||||||
|
if (!EqualStrings(aCx, trustedCode, aString, &equals)) {
|
||||||
|
// Propagate the failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!equals) {
|
||||||
|
isTrusted = false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (!comparePropertyAndString(aBodyArg, aBodyString)) {
|
||||||
|
// Propagate the failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isTrusted) {
|
||||||
|
MOZ_ASSERT(aParameterArgs.length() == aParameterStrings.length());
|
||||||
|
for (size_t index = 0; index < aParameterArgs.length(); index++) {
|
||||||
|
if (!comparePropertyAndString(aParameterArgs[index],
|
||||||
|
aParameterStrings[index])) {
|
||||||
|
// Propagate the failure.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!isTrusted) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Allow compilation if arguments are trusted.
|
||||||
|
*aOutCanCompileStrings = isTrusted;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
END_TEST(testDynamicCodeBrandChecks_CustomHostEnsureCanCompileStrings)
|
||||||
|
|
||||||
|
BEGIN_TEST(testDynamicCodeBrandChecks_RejectObjectForEval) {
|
||||||
|
JSSecurityCallbacks securityCallbacksRejectObjectBody = {
|
||||||
|
DisallowObjectsAndFailOtherwise, // contentSecurityPolicyAllows
|
||||||
|
ExtractTrustedCodeStringProperty, // codeForEvalGets
|
||||||
|
nullptr // subsumes
|
||||||
|
};
|
||||||
|
|
||||||
|
JS_SetSecurityCallbacks(cx, &securityCallbacksRejectObjectBody);
|
||||||
|
JS::RootedValue v(cx);
|
||||||
|
|
||||||
|
EXEC(customTypesSnippet);
|
||||||
|
|
||||||
|
// With the specified security callbacks, eval() will always fail.
|
||||||
|
CHECK(!execDontReport("eval('5*8))", __FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
CHECK(!execDontReport("eval(new TrustedType('5*8'))", __FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool DisallowObjectsAndFailOtherwise(
|
||||||
|
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
|
||||||
|
JS::CompilationType aCompilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
|
||||||
|
JS::Handle<JSString*> aBodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
|
||||||
|
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) {
|
||||||
|
if (aBodyArg.isObject()) {
|
||||||
|
// Disallow compilation for objects.
|
||||||
|
*aOutCanCompileStrings = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Otherwise, emulate a failure.
|
||||||
|
JS_ReportErrorASCII(aCx, "aBodyArg is not an Object");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
END_TEST(testDynamicCodeBrandChecks_RejectObjectForEval)
|
||||||
|
|||||||
@@ -1233,15 +1233,24 @@ bool JSContext::isThrowingDebuggeeWouldRun() {
|
|||||||
JSEXN_DEBUGGEEWOULDRUN;
|
JSEXN_DEBUGGEEWOULDRUN;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JSContext::isRuntimeCodeGenEnabled(JS::RuntimeCode kind,
|
bool JSContext::isRuntimeCodeGenEnabled(
|
||||||
HandleString code) {
|
JS::RuntimeCode kind, JS::Handle<JSString*> codeString,
|
||||||
|
JS::CompilationType compilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> parameterStrings,
|
||||||
|
JS::Handle<JSString*> bodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> parameterArgs,
|
||||||
|
JS::Handle<JS::Value> bodyArg, bool* outCanCompileStrings) {
|
||||||
// Make sure that the CSP callback is installed and that it permits runtime
|
// Make sure that the CSP callback is installed and that it permits runtime
|
||||||
// code generation.
|
// code generation.
|
||||||
if (JSCSPEvalChecker allows =
|
if (JSCSPEvalChecker allows =
|
||||||
runtime()->securityCallbacks->contentSecurityPolicyAllows) {
|
runtime()->securityCallbacks->contentSecurityPolicyAllows) {
|
||||||
return allows(this, kind, code);
|
return allows(this, kind, codeString, compilationType, parameterStrings,
|
||||||
|
bodyString, parameterArgs, bodyArg, outCanCompileStrings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Default implementation from the "Dynamic Code Brand Checks" spec.
|
||||||
|
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostensurecancompilestrings
|
||||||
|
*outCanCompileStrings = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -803,7 +803,13 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext,
|
|||||||
|
|
||||||
// Checks if the page's Content-Security-Policy (CSP) allows
|
// Checks if the page's Content-Security-Policy (CSP) allows
|
||||||
// runtime code generation "unsafe-eval", or "wasm-unsafe-eval" for Wasm.
|
// runtime code generation "unsafe-eval", or "wasm-unsafe-eval" for Wasm.
|
||||||
bool isRuntimeCodeGenEnabled(JS::RuntimeCode kind, js::HandleString code);
|
bool isRuntimeCodeGenEnabled(
|
||||||
|
JS::RuntimeCode kind, JS::Handle<JSString*> codeString,
|
||||||
|
JS::CompilationType compilationType,
|
||||||
|
JS::Handle<JS::StackGCVector<JSString*>> parameterStrings,
|
||||||
|
JS::Handle<JSString*> bodyString,
|
||||||
|
JS::Handle<JS::StackGCVector<JS::Value>> parameterArgs,
|
||||||
|
JS::Handle<JS::Value> bodyArg, bool* outCanCompileStrings);
|
||||||
|
|
||||||
// Get code to be used by eval for Object argument.
|
// Get code to be used by eval for Object argument.
|
||||||
bool getCodeForEval(JS::HandleObject code,
|
bool getCodeForEval(JS::HandleObject code,
|
||||||
|
|||||||
@@ -1346,19 +1346,32 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
if (args.length() > 1) {
|
if (args.length() > 1) {
|
||||||
RootedString str(cx);
|
RootedString str(cx);
|
||||||
|
|
||||||
// Steps 10, 14.d.
|
// Steps 10, 14.d.
|
||||||
unsigned n = args.length() - 1;
|
unsigned n = args.length() - 1;
|
||||||
|
if (!parameterStrings.reserve(n) || !parameterArgs.reserve(n)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
for (unsigned i = 0; i < n; i++) {
|
for (unsigned i = 0; i < n; i++) {
|
||||||
|
if (!parameterArgs.append(args[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Steps 14.a-b, 14.d.i-ii.
|
// Steps 14.a-b, 14.d.i-ii.
|
||||||
str = ToString<CanGC>(cx, args[i]);
|
str = ToString<CanGC>(cx, args[i]);
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parameterStrings.append(str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Steps 14.b, 14.d.iii.
|
// Steps 14.b, 14.d.iii.
|
||||||
if (!sb.append(str)) {
|
if (!sb.append(str)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1386,10 +1399,13 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS::RootedValue bodyArg(cx);
|
||||||
|
RootedString bodyString(cx);
|
||||||
if (args.length() > 0) {
|
if (args.length() > 0) {
|
||||||
// Steps 13, 14.e, 15.
|
// Steps 13, 14.e, 15.
|
||||||
RootedString body(cx, ToString<CanGC>(cx, args[args.length() - 1]));
|
bodyArg = args[args.length() - 1];
|
||||||
if (!body || !sb.append(body)) {
|
bodyString = ToString<CanGC>(cx, bodyArg);
|
||||||
|
if (!bodyString || !sb.append(bodyString)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1410,7 +1426,14 @@ static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Block this call if security callbacks forbid it.
|
// Block this call if security callbacks forbid it.
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, functionText)) {
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, functionText,
|
||||||
|
JS::CompilationType::Function,
|
||||||
|
parameterStrings, bodyString, parameterArgs,
|
||||||
|
bodyArg, &canCompileStrings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_FUNCTION);
|
JSMSG_CSP_BLOCKED_FUNCTION);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1619,7 +1619,16 @@ bool WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
|
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
|
||||||
return false;
|
return false;
|
||||||
@@ -4437,7 +4446,16 @@ static bool WebAssembly_compile(JSContext* cx, unsigned argc, Value* vp) {
|
|||||||
|
|
||||||
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
||||||
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return RejectWithPendingException(cx, promise, callArgs);
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.compile");
|
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.compile");
|
||||||
return RejectWithPendingException(cx, promise, callArgs);
|
return RejectWithPendingException(cx, promise, callArgs);
|
||||||
@@ -4521,7 +4539,16 @@ static bool WebAssembly_instantiate(JSContext* cx, unsigned argc, Value* vp) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return RejectWithPendingException(cx, promise, callArgs);
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM,
|
JSMSG_CSP_BLOCKED_WASM,
|
||||||
"WebAssembly.instantiate");
|
"WebAssembly.instantiate");
|
||||||
@@ -5114,7 +5141,16 @@ static bool WebAssembly_compileStreaming(JSContext* cx, unsigned argc,
|
|||||||
|
|
||||||
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
||||||
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return RejectWithPendingException(cx, resultPromise, callArgs);
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM,
|
JSMSG_CSP_BLOCKED_WASM,
|
||||||
"WebAssembly.compileStreaming");
|
"WebAssembly.compileStreaming");
|
||||||
@@ -5147,7 +5183,16 @@ static bool WebAssembly_instantiateStreaming(JSContext* cx, unsigned argc,
|
|||||||
|
|
||||||
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
CallArgs callArgs = CallArgsFromVp(argc, vp);
|
||||||
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return RejectWithPendingException(cx, resultPromise, callArgs);
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM,
|
JSMSG_CSP_BLOCKED_WASM,
|
||||||
"WebAssembly.instantiateStreaming");
|
"WebAssembly.instantiateStreaming");
|
||||||
|
|||||||
@@ -206,7 +206,16 @@ JSObject* Module::createObject(JSContext* cx) const {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr)) {
|
JS::RootedVector<JSString*> parameterStrings(cx);
|
||||||
|
JS::RootedVector<Value> parameterArgs(cx);
|
||||||
|
bool canCompileStrings = false;
|
||||||
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::WASM, nullptr,
|
||||||
|
JS::CompilationType::Undefined,
|
||||||
|
parameterStrings, nullptr, parameterArgs,
|
||||||
|
NullHandleValue, &canCompileStrings)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!canCompileStrings) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
|
JSMSG_CSP_BLOCKED_WASM, "WebAssembly.Module");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|||||||
Reference in New Issue
Block a user