tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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)