Bug 1905239 - Introduce HostGetCodeForEval hook for PerformEval. r=tschuster
See https://tc39.es/proposal-dynamic-code-brand-checks Differential Revision: https://phabricator.services.mozilla.com/D229477
This commit is contained in:
@@ -1554,6 +1554,7 @@ void nsScriptSecurityManager::InitJSCallbacks(JSContext* aCx) {
|
|||||||
|
|
||||||
static const JSSecurityCallbacks securityCallbacks = {
|
static const JSSecurityCallbacks securityCallbacks = {
|
||||||
ContentSecurityPolicyPermitsJSAction,
|
ContentSecurityPolicyPermitsJSAction,
|
||||||
|
nullptr, // codeForEvalGets
|
||||||
JSPrincipalsSubsume,
|
JSPrincipalsSubsume,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -92,8 +92,24 @@ enum class RuntimeCode { JS, WASM };
|
|||||||
typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::RuntimeCode kind,
|
typedef bool (*JSCSPEvalChecker)(JSContext* cx, JS::RuntimeCode kind,
|
||||||
JS::HandleString code);
|
JS::HandleString code);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Provide a string of code from an Object argument, to be used by eval.
|
||||||
|
* See JSContext::getCodeForEval() in vm/JSContext.cpp as well as
|
||||||
|
* https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostgetcodeforeval
|
||||||
|
*
|
||||||
|
* `code` is the JavaScript object passed by the user.
|
||||||
|
* `outCode` is the JavaScript string to be actually executed, with nullptr
|
||||||
|
* meaning NO-CODE.
|
||||||
|
*
|
||||||
|
* Return false on failure, true on success. The |outCode| parameter should not
|
||||||
|
* be modified in case of failure.
|
||||||
|
*/
|
||||||
|
typedef bool (*JSCodeForEvalOp)(JSContext* cx, JS::HandleObject code,
|
||||||
|
JS::MutableHandle<JSString*> outCode);
|
||||||
|
|
||||||
struct JSSecurityCallbacks {
|
struct JSSecurityCallbacks {
|
||||||
JSCSPEvalChecker contentSecurityPolicyAllows;
|
JSCSPEvalChecker contentSecurityPolicyAllows;
|
||||||
|
JSCodeForEvalOp codeForEvalGets;
|
||||||
JSSubsumesOp subsumes;
|
JSSubsumesOp subsumes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -239,21 +239,31 @@ static bool EvalKernel(JSContext* cx, HandleValue v, EvalType evalType,
|
|||||||
env->is<GlobalLexicalEnvironmentObject>());
|
env->is<GlobalLexicalEnvironmentObject>());
|
||||||
AssertInnerizedEnvironmentChain(cx, *env);
|
AssertInnerizedEnvironmentChain(cx, *env);
|
||||||
|
|
||||||
// Step 2.
|
// "Dynamic Code Brand Checks" adds support for Object values.
|
||||||
if (!v.isString()) {
|
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-performeval
|
||||||
|
// Steps 2-4.
|
||||||
|
RootedString str(cx);
|
||||||
|
if (v.isString()) {
|
||||||
|
str = v.toString();
|
||||||
|
} else if (v.isObject()) {
|
||||||
|
RootedObject obj(cx, &v.toObject());
|
||||||
|
if (!cx->getCodeForEval(obj, &str)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!str) {
|
||||||
vp.set(v);
|
vp.set(v);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Steps 3-4.
|
// Steps 6-8.
|
||||||
RootedString str(cx, v.toString());
|
|
||||||
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
|
if (!cx->isRuntimeCodeGenEnabled(JS::RuntimeCode::JS, str)) {
|
||||||
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
|
||||||
JSMSG_CSP_BLOCKED_EVAL);
|
JSMSG_CSP_BLOCKED_EVAL);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 5 ff.
|
// Step 9 ff.
|
||||||
|
|
||||||
// Per ES5, indirect eval runs in the global scope. (eval is specified this
|
// Per ES5, indirect eval runs in the global scope. (eval is specified this
|
||||||
// way so that the compiler can make assumptions about what bindings may or
|
// way so that the compiler can make assumptions about what bindings may or
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ UNIFIED_SOURCES += [
|
|||||||
"testDeflateStringToUTF8Buffer.cpp",
|
"testDeflateStringToUTF8Buffer.cpp",
|
||||||
"testDeleteProperty.cpp",
|
"testDeleteProperty.cpp",
|
||||||
"testDifferentNewTargetInvokeConstructor.cpp",
|
"testDifferentNewTargetInvokeConstructor.cpp",
|
||||||
|
"testDynamicCodeBrandChecks.cpp",
|
||||||
"testEmptyWindowIsOmitted.cpp",
|
"testEmptyWindowIsOmitted.cpp",
|
||||||
"testErrorCopying.cpp",
|
"testErrorCopying.cpp",
|
||||||
"testErrorLineOfContext.cpp",
|
"testErrorLineOfContext.cpp",
|
||||||
|
|||||||
94
js/src/jsapi-tests/testDynamicCodeBrandChecks.cpp
Normal file
94
js/src/jsapi-tests/testDynamicCodeBrandChecks.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||||
|
* vim: set ts=8 sts=2 et sw=2 tw=80:
|
||||||
|
*
|
||||||
|
* Tests that the column number of error reports is properly copied over from
|
||||||
|
* other reports when invoked from the C++ api.
|
||||||
|
*/
|
||||||
|
/* 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 "jsapi-tests/tests.h"
|
||||||
|
|
||||||
|
BEGIN_TEST(testDynamicCodeBrandChecks_DefaultHostGetCodeForEval) {
|
||||||
|
JS::RootedValue v(cx);
|
||||||
|
|
||||||
|
// String arguments are evaluated.
|
||||||
|
EVAL("eval('5*8');", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 40);
|
||||||
|
|
||||||
|
// Other arguments are returned as is by eval.
|
||||||
|
EVAL("eval({myProp: 41});", &v);
|
||||||
|
CHECK(v.isObject());
|
||||||
|
JS::RootedObject obj(cx, &v.toObject());
|
||||||
|
JS::RootedValue myProp(cx);
|
||||||
|
CHECK(JS_GetProperty(cx, obj, "myProp", &myProp));
|
||||||
|
CHECK(myProp.isNumber() && myProp.toNumber() == 41);
|
||||||
|
|
||||||
|
EVAL("eval({trustedCode: '6*7'}).trustedCode;", &v);
|
||||||
|
CHECK(v.isString());
|
||||||
|
JSString* str = v.toString();
|
||||||
|
CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "6*7"));
|
||||||
|
|
||||||
|
EVAL("eval({trustedCode: 42}).trustedCode;", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 42);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
END_TEST(testDynamicCodeBrandChecks_DefaultHostGetCodeForEval)
|
||||||
|
|
||||||
|
static bool ExtractTrustedCodeStringProperty(
|
||||||
|
JSContext* aCx, JS::Handle<JSObject*> aCode,
|
||||||
|
JS::MutableHandle<JSString*> outCode) {
|
||||||
|
JS::RootedValue value(aCx);
|
||||||
|
if (!JS_GetProperty(aCx, aCode, "trustedCode", &value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value.isUndefined()) {
|
||||||
|
// If the property is undefined, return NO-CODE.
|
||||||
|
outCode.set(nullptr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (value.isString()) {
|
||||||
|
// If the property is a string, return it.
|
||||||
|
outCode.set(value.toString());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Otherwise, emulate a failure.
|
||||||
|
JS_ReportErrorASCII(aCx, "Unsupported value for trustedCode property");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BEGIN_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval) {
|
||||||
|
JSSecurityCallbacks securityCallbacksWithEvalAcceptingObject = {
|
||||||
|
nullptr, // contentSecurityPolicyAllows
|
||||||
|
ExtractTrustedCodeStringProperty, // codeForEvalGets
|
||||||
|
nullptr // subsumes
|
||||||
|
};
|
||||||
|
JS_SetSecurityCallbacks(cx, &securityCallbacksWithEvalAcceptingObject);
|
||||||
|
JS::RootedValue v(cx);
|
||||||
|
|
||||||
|
// String arguments are evaluated.
|
||||||
|
EVAL("eval('5*8');", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 40);
|
||||||
|
|
||||||
|
// Other arguments are returned as is by eval...
|
||||||
|
EVAL("eval({myProp: 41});", &v);
|
||||||
|
CHECK(v.isObject());
|
||||||
|
JS::RootedObject obj(cx, &v.toObject());
|
||||||
|
JS::RootedValue myProp(cx);
|
||||||
|
CHECK(JS_GetProperty(cx, obj, "myProp", &myProp));
|
||||||
|
CHECK(myProp.isNumber() && myProp.toNumber() == 41);
|
||||||
|
|
||||||
|
// ... but Objects are first tentatively converted to String by the
|
||||||
|
// codeForEvalGets callback.
|
||||||
|
EVAL("eval({trustedCode: '6*7'});", &v);
|
||||||
|
CHECK(v.isNumber() && v.toNumber() == 6 * 7);
|
||||||
|
|
||||||
|
// And if that codeForEvalGets callback fails, then so does the eval call.
|
||||||
|
CHECK(!execDontReport("eval({trustedCode: 6*7});", __FILE__, __LINE__));
|
||||||
|
cx->clearPendingException();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
END_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval)
|
||||||
@@ -258,6 +258,7 @@ struct StructuredCloneTestPrincipals final : public JSPrincipals {
|
|||||||
|
|
||||||
JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
|
JSSecurityCallbacks StructuredCloneTestPrincipals::securityCallbacks = {
|
||||||
nullptr, // contentSecurityPolicyAllows
|
nullptr, // contentSecurityPolicyAllows
|
||||||
|
nullptr, // codeForEvalGets
|
||||||
subsumes};
|
subsumes};
|
||||||
|
|
||||||
BEGIN_TEST(testStructuredClone_SavedFrame) {
|
BEGIN_TEST(testStructuredClone_SavedFrame) {
|
||||||
|
|||||||
@@ -955,6 +955,7 @@ class ShellPrincipals final : public JSPrincipals {
|
|||||||
|
|
||||||
JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
|
JSSecurityCallbacks ShellPrincipals::securityCallbacks = {
|
||||||
nullptr, // contentSecurityPolicyAllows
|
nullptr, // contentSecurityPolicyAllows
|
||||||
|
nullptr, // codeForEvalGets
|
||||||
subsumes};
|
subsumes};
|
||||||
|
|
||||||
// The fully-trusted principal subsumes all other principals.
|
// The fully-trusted principal subsumes all other principals.
|
||||||
|
|||||||
@@ -1245,6 +1245,17 @@ bool JSContext::isRuntimeCodeGenEnabled(JS::RuntimeCode kind,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool JSContext::getCodeForEval(HandleObject code,
|
||||||
|
JS::MutableHandle<JSString*> outCode) {
|
||||||
|
if (JSCodeForEvalOp gets = runtime()->securityCallbacks->codeForEvalGets) {
|
||||||
|
return gets(this, code, outCode);
|
||||||
|
}
|
||||||
|
// Default implementation from the "Dynamic Code Brand Checks" spec.
|
||||||
|
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostgetcodeforeval
|
||||||
|
outCode.set(nullptr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
size_t JSContext::sizeOfExcludingThis(
|
size_t JSContext::sizeOfExcludingThis(
|
||||||
mozilla::MallocSizeOf mallocSizeOf) const {
|
mozilla::MallocSizeOf mallocSizeOf) const {
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -805,6 +805,10 @@ struct JS_PUBLIC_API JSContext : public JS::RootingContext,
|
|||||||
// 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::HandleString code);
|
||||||
|
|
||||||
|
// Get code to be used by eval for Object argument.
|
||||||
|
bool getCodeForEval(JS::HandleObject code,
|
||||||
|
JS::MutableHandle<JSString*> outCode);
|
||||||
|
|
||||||
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||||
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user