Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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)
// 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
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)