tor-browser

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

test_objectgrips-21.js (12444B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
      7 
      8 registerCleanupFunction(() => {
      9  Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
     10 });
     11 
     12 // Run test_unsafe_grips twice, one against a system principal debuggee
     13 // and another time with a null principal debuggee
     14 
     15 // The following tests work like this:
     16 // - The specified code is evaluated in a system principal.
     17 //   `Cu`, `systemPrincipal` and `Services` are provided as global variables.
     18 // - The resulting object is debugged in a system or null principal debuggee,
     19 //   depending on in which list the test is placed.
     20 //   It is tested according to the specified test parameters.
     21 // - An ordinary object that inherits from the resulting one is also debugged.
     22 //   This is just to check that it can be normally debugged even with an unsafe
     23 //   object in the prototype. The specified test parameters do not apply.
     24 
     25 // The following tests are defined via properties with the following defaults.
     26 const defaults = {
     27  // The class of the grip.
     28  class: "Restricted",
     29 
     30  // The stringification of the object
     31  string: "",
     32 
     33  // Whether the object (not its grip) has class "Function".
     34  isFunction: false,
     35 
     36  // Whether the grip has a preview property.
     37  hasPreview: true,
     38 
     39  // Code that assigns the object to be tested into the obj variable.
     40  code: "var obj = {}",
     41 
     42  // The type of the grip of the prototype.
     43  protoType: "null",
     44 
     45  // Whether the object has some own string properties.
     46  hasOwnPropertyNames: false,
     47 
     48  // Whether the object has some own symbol properties.
     49  hasOwnPropertySymbols: false,
     50 
     51  // The descriptor obtained when retrieving property "x" or Symbol("x").
     52  property: undefined,
     53 
     54  // Code evaluated after the test, whose result is expected to be true.
     55  afterTest: "true == true",
     56 };
     57 
     58 // The following tests use a system principal debuggee.
     59 const systemPrincipalTests = [
     60  {
     61    // Dead objects throw a TypeError when accessing properties.
     62    class: "DeadObject",
     63    string: "<dead object>",
     64    code: `
     65    var obj = Cu.Sandbox(null);
     66    Cu.nukeSandbox(obj);
     67  `,
     68    property: descriptor({ value: "TypeError" }),
     69  },
     70  {
     71    // This proxy checks that no trap runs (using a second proxy as the handler
     72    // there is no need to maintain a list of all possible traps).
     73    class: "Proxy",
     74    string: "<proxy>",
     75    code: `
     76    var trapDidRun = false;
     77    var obj = new Proxy({}, new Proxy({}, {get: (_, trap) => {
     78      trapDidRun = true;
     79      throw new Error("proxy trap '" + trap + "' was called.");
     80    }}));
     81  `,
     82    afterTest: "trapDidRun === false",
     83  },
     84  {
     85    // Like the previous test, but now the proxy has a Function class.
     86    class: "Proxy",
     87    string: "<proxy>",
     88    isFunction: true,
     89    code: `
     90    var trapDidRun = false;
     91    var obj = new Proxy(function(){}, new Proxy({}, {get: (_, trap) => {
     92      trapDidRun = true;
     93      throw new Error("proxy trap '" + trap + "' was called.(function)");
     94    }}));
     95  `,
     96    afterTest: "trapDidRun === false",
     97  },
     98  {
     99    // Invisisible-to-debugger objects can't be unwrapped, so we don't know if
    100    // they are proxies. Thus they shouldn't be accessed.
    101    class: "InvisibleToDebugger: Array",
    102    string: "<invisibleToDebugger>",
    103    hasPreview: false,
    104    code: `
    105    var s = Cu.Sandbox(systemPrincipal, {invisibleToDebugger: true});
    106    var obj = s.eval("[1, 2, 3]");
    107  `,
    108  },
    109  {
    110    // Like the previous test, but now the object has a Function class.
    111    class: "InvisibleToDebugger: Function",
    112    string: "<invisibleToDebugger>",
    113    isFunction: true,
    114    hasPreview: false,
    115    code: `
    116    var s = Cu.Sandbox(systemPrincipal, {invisibleToDebugger: true});
    117    var obj = s.eval("(function func(arg){})");
    118  `,
    119  },
    120  {
    121    // Cu.Sandbox is a WrappedNative that throws when accessing properties.
    122    class: "nsXPCComponents_utils_Sandbox",
    123    string: "[object nsXPCComponents_utils_Sandbox]",
    124    code: `var obj = Cu.Sandbox;`,
    125    protoType: "object",
    126  },
    127 ];
    128 
    129 // The following tests run code in a system principal, but the resulting object
    130 // is debugged in a null principal.
    131 const nullPrincipalTests = [
    132  {
    133    // The null principal gets undefined when attempting to access properties.
    134    string: "[object Object]",
    135    code: `var obj = {x: -1};`,
    136  },
    137  {
    138    // For arrays it's an error instead of undefined.
    139    string: "[object Object]",
    140    code: `var obj = [1, 2, 3];`,
    141    property: descriptor({ value: "Error" }),
    142  },
    143  {
    144    // For functions it's also an error.
    145    string: "function func(arg){}",
    146    isFunction: true,
    147    hasPreview: false,
    148    code: `var obj = function func(arg){};`,
    149    property: descriptor({ value: "Error" }),
    150  },
    151  {
    152    // Check that no proxy trap runs.
    153    string: "[object Object]",
    154    code: `
    155    var trapDidRun = false;
    156    var obj = new Proxy([], new Proxy({}, {get: (_, trap) => {
    157      trapDidRun = true;
    158      throw new Error("proxy trap '" + trap + "' was called.");
    159    }}));
    160  `,
    161    property: descriptor({ value: "Error" }),
    162    afterTest: `trapDidRun === false`,
    163  },
    164  {
    165    // Like the previous test, but now the object is a callable Proxy.
    166    string: "function () {\n    [native code]\n}",
    167    isFunction: true,
    168    hasPreview: false,
    169    code: `
    170    var trapDidRun = false;
    171    var obj = new Proxy(function(){}, new Proxy({}, {get: (_, trap) => {
    172      trapDidRun = true;
    173      throw new Error("proxy trap '" + trap + "' was called.");
    174    }}));
    175  `,
    176    property: descriptor({ value: "Error" }),
    177    afterTest: `trapDidRun === false`,
    178  },
    179  {
    180    // Cross-origin Window objects do expose some properties and have a preview.
    181    string: "[object Object]",
    182    code: `var obj = Services.appShell.createWindowlessBrowser().document.defaultView;`,
    183    hasOwnPropertyNames: true,
    184    hasOwnPropertySymbols: true,
    185    property: descriptor({ value: "SecurityError" }),
    186    previewUrl: "about:blank",
    187  },
    188  {
    189    // Cross-origin Location objects do expose some properties and have a preview.
    190    string: "[object Object]",
    191    code: `var obj = Services.appShell.createWindowlessBrowser().document.defaultView
    192                   .location;`,
    193    hasOwnPropertyNames: true,
    194    hasOwnPropertySymbols: true,
    195    property: descriptor({ value: "SecurityError" }),
    196  },
    197 ];
    198 
    199 function descriptor(descr) {
    200  return Object.assign(
    201    {
    202      configurable: false,
    203      writable: false,
    204      enumerable: false,
    205      value: undefined,
    206    },
    207    descr
    208  );
    209 }
    210 
    211 async function test_unsafe_grips(
    212  { threadFront, debuggee, isWorkerServer },
    213  tests
    214 ) {
    215  debuggee.eval(
    216    // These arguments are tested.
    217    // eslint-disable-next-line no-unused-vars
    218    function stopMe(arg1, arg2) {
    219      debugger;
    220    }.toString()
    221  );
    222 
    223  for (let data of tests) {
    224    data = { ...defaults, ...data };
    225 
    226    // Run the code and test the results.
    227    const sandbox = Cu.Sandbox(systemPrincipal);
    228    Object.assign(sandbox, { Services, systemPrincipal, Cu });
    229    sandbox.eval(data.code);
    230    debuggee.obj = sandbox.obj;
    231    const inherits = `Object.create(obj, {
    232        x: {value: 1},
    233        [Symbol.for("x")]: {value: 2}
    234      })`;
    235 
    236    const packet = await executeOnNextTickAndWaitForPause(
    237      () => debuggee.eval(`stopMe(obj, ${inherits});`),
    238      threadFront
    239    );
    240 
    241    const [objGrip, inheritsGrip] = packet.frame.arguments;
    242    for (const grip of [objGrip, inheritsGrip]) {
    243      const isUnsafe = grip === objGrip;
    244      // If `isUnsafe` is true, the parameters in `data` will be used to assert
    245      // against `objGrip`, the grip of the object `obj` created by the test.
    246      // Otherwise, the grip will refer to `inherits`, an ordinary object which
    247      // inherits from `obj`. Then all checks are hardcoded because in every test
    248      // all methods are expected to work the same on `inheritsGrip`.
    249      check_grip(grip, data, isUnsafe, isWorkerServer);
    250 
    251      const objClient = threadFront.pauseGrip(grip);
    252      let response, slice;
    253 
    254      response = await objClient.getPrototypeAndProperties();
    255      check_properties(response.ownProperties, data, isUnsafe);
    256      check_symbols(response.ownSymbols, data, isUnsafe);
    257      check_prototype(response.prototype, data, isUnsafe, isWorkerServer);
    258 
    259      response = await objClient.enumProperties({
    260        ignoreIndexedProperties: true,
    261      });
    262      slice = await response.slice(0, response.count);
    263      check_properties(slice.ownProperties, data, isUnsafe);
    264 
    265      response = await objClient.enumProperties({});
    266      slice = await response.slice(0, response.count);
    267      check_properties(slice.ownProperties, data, isUnsafe);
    268 
    269      response = await objClient.getProperty("x");
    270      check_property(response.descriptor, data, isUnsafe);
    271 
    272      response = await objClient.enumSymbols();
    273      slice = await response.slice(0, response.count);
    274      check_symbol_names(slice.ownSymbols, data, isUnsafe);
    275 
    276      response = await objClient.getProperty(Symbol.for("x"));
    277      check_symbol(response.descriptor, data, isUnsafe);
    278 
    279      response = await objClient.getPrototype();
    280      check_prototype(response.prototype, data, isUnsafe, isWorkerServer);
    281    }
    282 
    283    await threadFront.resume();
    284 
    285    ok(sandbox.eval(data.afterTest), "Check after test passes");
    286  }
    287 }
    288 
    289 function check_grip(grip, data, isUnsafe, isWorkerServer) {
    290  if (isUnsafe) {
    291    strictEqual(grip.class, data.class, "The grip has the proper class.");
    292    strictEqual("preview" in grip, data.hasPreview, "Check preview presence.");
    293    // preview.url isn't populated on worker server.
    294    if (data.previewUrl && !isWorkerServer) {
    295      console.trace();
    296      strictEqual(
    297        grip.preview.url,
    298        data.previewUrl,
    299        `Check preview.url for "${data.code}".`
    300      );
    301    }
    302  } else {
    303    strictEqual(grip.class, "Object", "The grip has 'Object' class.");
    304    ok("preview" in grip, "The grip has a preview.");
    305  }
    306 }
    307 
    308 function check_properties(props, data, isUnsafe) {
    309  const propNames = Reflect.ownKeys(props);
    310  check_property_names(propNames, data, isUnsafe);
    311  if (isUnsafe) {
    312    deepEqual(props.x, undefined, "The property does not exist.");
    313  } else {
    314    strictEqual(props.x.value, 1, "The property has the right value.");
    315  }
    316 }
    317 
    318 function check_property_names(props, data, isUnsafe) {
    319  if (isUnsafe) {
    320    strictEqual(
    321      !!props.length,
    322      data.hasOwnPropertyNames,
    323      "Check presence of own string properties."
    324    );
    325  } else {
    326    strictEqual(props.length, 1, "1 own property was retrieved.");
    327    strictEqual(props[0], "x", "The property has the right name.");
    328  }
    329 }
    330 
    331 function check_property(descr, data, isUnsafe) {
    332  if (isUnsafe) {
    333    deepEqual(descr, data.property, "Got the right property descriptor.");
    334  } else {
    335    strictEqual(descr.value, 1, "The property has the right value.");
    336  }
    337 }
    338 
    339 function check_symbols(symbols, data, isUnsafe) {
    340  check_symbol_names(symbols, data, isUnsafe);
    341  if (!isUnsafe) {
    342    check_symbol(symbols[0].descriptor, data, isUnsafe);
    343  }
    344 }
    345 
    346 function check_symbol_names(props, data, isUnsafe) {
    347  if (isUnsafe) {
    348    strictEqual(
    349      !!props.length,
    350      data.hasOwnPropertySymbols,
    351      "Check presence of own symbol properties."
    352    );
    353  } else {
    354    strictEqual(props.length, 1, "1 own symbol property was retrieved.");
    355    strictEqual(props[0].name, "Symbol(x)", "The symbol has the right name.");
    356  }
    357 }
    358 
    359 function check_symbol(descr, data, isUnsafe) {
    360  if (isUnsafe) {
    361    deepEqual(
    362      descr,
    363      data.property,
    364      "Got the right symbol property descriptor."
    365    );
    366  } else {
    367    strictEqual(descr.value, 2, "The symbol property has the right value.");
    368  }
    369 }
    370 
    371 function check_prototype(proto, data, isUnsafe, isWorkerServer) {
    372  const protoGrip = proto && proto.getGrip ? proto.getGrip() : proto;
    373  if (isUnsafe) {
    374    deepEqual(protoGrip.type, data.protoType, "Got the right prototype type.");
    375  } else {
    376    check_grip(protoGrip, data, true, isWorkerServer);
    377  }
    378 }
    379 
    380 // threadFrontTest uses systemPrincipal by default, but let's be explicit here.
    381 add_task(
    382  threadFrontTest(
    383    options => {
    384      return test_unsafe_grips(options, systemPrincipalTests, "system");
    385    },
    386    { principal: systemPrincipal }
    387  )
    388 );
    389 
    390 const nullPrincipal = Services.scriptSecurityManager.createNullPrincipal({});
    391 add_task(
    392  threadFrontTest(
    393    options => {
    394      return test_unsafe_grips(options, nullPrincipalTests, "null");
    395    },
    396    { principal: nullPrincipal }
    397  )
    398 );