testDynamicCodeBrandChecks.cpp (10193B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- 2 * vim: set ts=8 sts=2 et sw=2 tw=80: 3 * 4 * Tests that the column number of error reports is properly copied over from 5 * other reports when invoked from the C++ api. 6 */ 7 /* This Source Code Form is subject to the terms of the Mozilla Public 8 * License, v. 2.0. If a copy of the MPL was not distributed with this 9 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 10 11 #include "jsapi-tests/tests.h" 12 13 BEGIN_TEST(testDynamicCodeBrandChecks_DefaultHostGetCodeForEval) { 14 JS::RootedValue v(cx); 15 16 // String arguments are evaluated. 17 EVAL("eval('5*8');", &v); 18 CHECK(v.isNumber() && v.toNumber() == 40); 19 20 // Other arguments are returned as is by eval. 21 EVAL("eval({myProp: 41});", &v); 22 CHECK(v.isObject()); 23 JS::RootedObject obj(cx, &v.toObject()); 24 JS::RootedValue myProp(cx); 25 CHECK(JS_GetProperty(cx, obj, "myProp", &myProp)); 26 CHECK(myProp.isNumber() && myProp.toNumber() == 41); 27 28 EVAL("eval({trustedCode: '6*7'}).trustedCode;", &v); 29 CHECK(v.isString()); 30 JSString* str = v.toString(); 31 CHECK(JS_LinearStringEqualsLiteral(JS_ASSERT_STRING_IS_LINEAR(str), "6*7")); 32 33 EVAL("eval({trustedCode: 42}).trustedCode;", &v); 34 CHECK(v.isNumber() && v.toNumber() == 42); 35 36 return true; 37 } 38 END_TEST(testDynamicCodeBrandChecks_DefaultHostGetCodeForEval) 39 40 static bool ExtractTrustedCodeStringProperty( 41 JSContext* aCx, JS::Handle<JSObject*> aCode, 42 JS::MutableHandle<JSString*> outCode) { 43 JS::RootedValue value(aCx); 44 if (!JS_GetProperty(aCx, aCode, "trustedCode", &value)) { 45 return false; 46 } 47 if (value.isUndefined()) { 48 // If the property is undefined, return NO-CODE. 49 outCode.set(nullptr); 50 return true; 51 } 52 if (value.isString()) { 53 // If the property is a string, return it. 54 outCode.set(value.toString()); 55 return true; 56 } 57 // Otherwise, emulate a failure. 58 JS_ReportErrorASCII(aCx, "Unsupported value for trustedCode property"); 59 return false; 60 } 61 62 BEGIN_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval) { 63 JSSecurityCallbacks securityCallbacksWithEvalAcceptingObject = { 64 nullptr, // contentSecurityPolicyAllows 65 ExtractTrustedCodeStringProperty, // codeForEvalGets 66 nullptr // subsumes 67 }; 68 JS_SetSecurityCallbacks(cx, &securityCallbacksWithEvalAcceptingObject); 69 JS::RootedValue v(cx); 70 71 // String arguments are evaluated. 72 EVAL("eval('5*8');", &v); 73 CHECK(v.isNumber() && v.toNumber() == 40); 74 75 // Other arguments are returned as is by eval... 76 EVAL("eval({myProp: 41});", &v); 77 CHECK(v.isObject()); 78 JS::RootedObject obj(cx, &v.toObject()); 79 JS::RootedValue myProp(cx); 80 CHECK(JS_GetProperty(cx, obj, "myProp", &myProp)); 81 CHECK(myProp.isNumber() && myProp.toNumber() == 41); 82 83 // ... but Objects are first tentatively converted to String by the 84 // codeForEvalGets callback. 85 EVAL("eval({trustedCode: '6*7'});", &v); 86 CHECK(v.isNumber() && v.toNumber() == 6 * 7); 87 88 // And if that codeForEvalGets callback fails, then so does the eval call. 89 CHECK(!execDontReport("eval({trustedCode: 6*7});", __FILE__, __LINE__)); 90 cx->clearPendingException(); 91 92 return true; 93 } 94 END_TEST(testDynamicCodeBrandChecks_CustomHostGetCodeForEval) 95 96 // This snippet defines a TrustedType that wraps some trustedCode string and 97 // stringifies to that string, as well as a helper to create a fake instance 98 // that can stringify to a different string. 99 const char* customTypesSnippet = 100 "function TrustedType(aTrustedCode) { this.trustedCode = aTrustedCode; };" 101 "TrustedType.prototype.toString = function() { return this.trustedCode; };" 102 "function CreateFakeTrustedType(aTrustedCode, aString) {" 103 " let fake = new TrustedType(aTrustedCode);" 104 " fake.toString = () => { return aString; };" 105 " return fake;" 106 "};"; 107 108 BEGIN_TEST(testDynamicCodeBrandChecks_CustomHostEnsureCanCompileStrings) { 109 JSSecurityCallbacks securityCallbacksWithCustomHostEnsureCanCompileStrings = { 110 StringifiedObjectsMatchTrustedCodeProperties, // contentSecurityPolicyAllows 111 ExtractTrustedCodeStringProperty, // codeForEvalGets 112 nullptr // subsumes 113 }; 114 JS_SetSecurityCallbacks( 115 cx, &securityCallbacksWithCustomHostEnsureCanCompileStrings); 116 JS::RootedValue v(cx); 117 118 EXEC(customTypesSnippet); 119 120 // String arguments are evaluated. 121 EVAL("eval('5*8');", &v); 122 CHECK(v.isNumber() && v.toNumber() == 40); 123 EVAL("(new Function('a', 'b', 'return a * b'))(6, 7);", &v); 124 CHECK(v.isNumber() && v.toNumber() == 42); 125 126 // The same works with TrustedType wrappers. 127 EVAL("eval(new TrustedType('5*8'));", &v); 128 CHECK(v.isNumber() && v.toNumber() == 40); 129 EVAL( 130 "(new Function(new TrustedType('a'), new TrustedType('b'), new " 131 "TrustedType('return a * b')))(6, 7);", 132 &v); 133 CHECK(v.isNumber() && v.toNumber() == 42); 134 135 // new Function fails if one of the stringified argument does not match the 136 // trustedCode property. 137 CHECK(!execDontReport( 138 "new Function(CreateFakeTrustedType('a', 'c'), 'b', 'return b');", 139 __FILE__, __LINE__)); 140 cx->clearPendingException(); 141 CHECK(!execDontReport( 142 "new Function('a', CreateFakeTrustedType('b', 'c'), 'return a');", 143 __FILE__, __LINE__)); 144 cx->clearPendingException(); 145 CHECK( 146 !execDontReport("new Function('a', 'b', CreateFakeTrustedType('return a " 147 "* b', 'return a + b'));", 148 __FILE__, __LINE__)); 149 cx->clearPendingException(); 150 151 // new Function also fails if StringifiedObjectsMatchTrustedCodeProperties 152 // returns false. 153 CHECK(!execDontReport("new Function('a', 'b', new TrustedType(undefined));", 154 __FILE__, __LINE__)); 155 cx->clearPendingException(); 156 157 // PerformEval relies on ExtractTrustedCodeProperty rather than toString() to 158 // obtain the code to execute, so StringifiedObjectsMatchTrustedCodeProperties 159 // will always allow the code execution for the specified security callbacks. 160 EVAL("eval(CreateFakeTrustedType('5*8', '6*7'));", &v); 161 CHECK(v.isNumber() && v.toNumber() == 40); 162 EVAL("eval(new TrustedType(undefined));", &v); 163 CHECK(v.isObject()); 164 JS::RootedObject obj(cx, &v.toObject()); 165 JS::RootedValue trustedCode(cx); 166 CHECK(JS_GetProperty(cx, obj, "trustedCode", &trustedCode)); 167 CHECK(trustedCode.isUndefined()); 168 169 return true; 170 } 171 172 // This is a HostEnsureCanCompileStrings() implementation similar to some checks 173 // described in the CSP spec: verify that aBodyString and aParameterStrings 174 // match the corresponding trustedCode property on aBodyArg and aParameterArgs 175 // objects. See https://w3c.github.io/webappsec-csp/#can-compile-strings 176 static bool StringifiedObjectsMatchTrustedCodeProperties( 177 JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString, 178 JS::CompilationType aCompilationType, 179 JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings, 180 JS::Handle<JSString*> aBodyString, 181 JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs, 182 JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) { 183 bool isTrusted = true; 184 auto comparePropertyAndString = [&aCx, &isTrusted]( 185 JS::Handle<JS::Value> aValue, 186 JS::Handle<JSString*> aString) { 187 if (!aValue.isObject()) { 188 // Just trust non-Objects. 189 return true; 190 } 191 JS::RootedObject obj(aCx, &aValue.toObject()); 192 193 JS::RootedString trustedCode(aCx); 194 if (!ExtractTrustedCodeStringProperty(aCx, obj, &trustedCode)) { 195 // Propagate the failure. 196 return false; 197 } 198 if (!trustedCode) { 199 // Emulate a failure if trustedCode is undefined. 200 JS_ReportErrorASCII(aCx, 201 "test failed, trustedCode property is undefined"); 202 return false; 203 } 204 bool equals; 205 if (!EqualStrings(aCx, trustedCode, aString, &equals)) { 206 // Propagate the failure. 207 return false; 208 } 209 if (!equals) { 210 isTrusted = false; 211 } 212 return true; 213 }; 214 if (!comparePropertyAndString(aBodyArg, aBodyString)) { 215 // Propagate the failure. 216 return false; 217 } 218 if (isTrusted) { 219 MOZ_ASSERT(aParameterArgs.length() == aParameterStrings.length()); 220 for (size_t index = 0; index < aParameterArgs.length(); index++) { 221 if (!comparePropertyAndString(aParameterArgs[index], 222 aParameterStrings[index])) { 223 // Propagate the failure. 224 return false; 225 } 226 if (!isTrusted) { 227 break; 228 } 229 } 230 } 231 // Allow compilation if arguments are trusted. 232 *aOutCanCompileStrings = isTrusted; 233 return true; 234 } 235 236 END_TEST(testDynamicCodeBrandChecks_CustomHostEnsureCanCompileStrings) 237 238 BEGIN_TEST(testDynamicCodeBrandChecks_RejectObjectForEval) { 239 JSSecurityCallbacks securityCallbacksRejectObjectBody = { 240 DisallowObjectsAndFailOtherwise, // contentSecurityPolicyAllows 241 ExtractTrustedCodeStringProperty, // codeForEvalGets 242 nullptr // subsumes 243 }; 244 245 JS_SetSecurityCallbacks(cx, &securityCallbacksRejectObjectBody); 246 JS::RootedValue v(cx); 247 248 EXEC(customTypesSnippet); 249 250 // With the specified security callbacks, eval() will always fail. 251 CHECK(!execDontReport("eval('5*8))", __FILE__, __LINE__)); 252 cx->clearPendingException(); 253 CHECK(!execDontReport("eval(new TrustedType('5*8'))", __FILE__, __LINE__)); 254 cx->clearPendingException(); 255 256 return true; 257 } 258 259 static bool DisallowObjectsAndFailOtherwise( 260 JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString, 261 JS::CompilationType aCompilationType, 262 JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings, 263 JS::Handle<JSString*> aBodyString, 264 JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs, 265 JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) { 266 if (aBodyArg.isObject()) { 267 // Disallow compilation for objects. 268 *aOutCanCompileStrings = false; 269 return true; 270 } 271 // Otherwise, emulate a failure. 272 JS_ReportErrorASCII(aCx, "aBodyArg is not an Object"); 273 return false; 274 } 275 276 END_TEST(testDynamicCodeBrandChecks_RejectObjectForEval)