tor-browser

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

test_objectgrips-17.js (10854B)


      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 async function testPrincipal(options, globalPrincipal, debuggeeHasXrays) {
     13  const { debuggee } = options;
     14  // Create a global object with the specified security principal.
     15  // If none is specified, use the debuggee.
     16  if (globalPrincipal === undefined) {
     17    await test(options, {
     18      global: debuggee,
     19      subsumes: true,
     20      isOpaque: false,
     21      globalIsInvisible: false,
     22    });
     23    return;
     24  }
     25 
     26  const debuggeePrincipal = Cu.getObjectPrincipal(debuggee);
     27  const sameOrigin = debuggeePrincipal.origin === globalPrincipal.origin;
     28  const subsumes = debuggeePrincipal.subsumes(globalPrincipal);
     29  for (const globalHasXrays of [true, false]) {
     30    const isOpaque =
     31      subsumes &&
     32      globalPrincipal !== systemPrincipal &&
     33      ((sameOrigin && debuggeeHasXrays) || globalHasXrays);
     34    for (const globalIsInvisible of [true, false]) {
     35      let global = Cu.Sandbox(globalPrincipal, {
     36        wantXrays: globalHasXrays,
     37        invisibleToDebugger: globalIsInvisible,
     38      });
     39      // Previously, the Sandbox constructor would (bizarrely) waive xrays on
     40      // the return Sandbox if wantXrays was false. This has now been fixed,
     41      // but we need to mimic that behavior here to make the test continue
     42      // to pass.
     43      if (!globalHasXrays) {
     44        global = Cu.waiveXrays(global);
     45      }
     46      await test(options, { global, subsumes, isOpaque, globalIsInvisible });
     47    }
     48  }
     49 }
     50 
     51 async function test({ threadFront, debuggee }, testOptions) {
     52  const { global } = testOptions;
     53  const packet = await executeOnNextTickAndWaitForPause(eval_code, threadFront);
     54  // Get the grips.
     55  const [proxyGrip, inheritsProxyGrip, inheritsProxy2Grip] =
     56    packet.frame.arguments;
     57 
     58  // Check the grip of the proxy object.
     59  check_proxy_grip(debuggee, testOptions, proxyGrip);
     60 
     61  // Check the target and handler slots of the proxy object.
     62  const proxyClient = threadFront.pauseGrip(proxyGrip);
     63  const proxySlots = await proxyClient.getProxySlots();
     64  check_proxy_slots(debuggee, testOptions, proxyGrip, proxySlots);
     65 
     66  // Check the prototype and properties of the proxy object.
     67  const proxyResponse = await proxyClient.getPrototypeAndProperties();
     68  check_properties(testOptions, proxyResponse.ownProperties, true, false);
     69  check_prototype(debuggee, testOptions, proxyResponse.prototype, true, false);
     70 
     71  // Check the prototype and properties of the object which inherits from the proxy.
     72  const inheritsProxyClient = threadFront.pauseGrip(inheritsProxyGrip);
     73  const inheritsProxyResponse =
     74    await inheritsProxyClient.getPrototypeAndProperties();
     75  check_properties(
     76    testOptions,
     77    inheritsProxyResponse.ownProperties,
     78    false,
     79    false
     80  );
     81  check_prototype(
     82    debuggee,
     83    testOptions,
     84    inheritsProxyResponse.prototype,
     85    false,
     86    false
     87  );
     88 
     89  // The prototype chain was not iterated if the object was inaccessible, so now check
     90  // another object which inherits from the proxy, but was created in the debuggee.
     91  const inheritsProxy2Client = threadFront.pauseGrip(inheritsProxy2Grip);
     92  const inheritsProxy2Response =
     93    await inheritsProxy2Client.getPrototypeAndProperties();
     94  check_properties(
     95    testOptions,
     96    inheritsProxy2Response.ownProperties,
     97    false,
     98    true
     99  );
    100  check_prototype(
    101    debuggee,
    102    testOptions,
    103    inheritsProxy2Response.prototype,
    104    false,
    105    true
    106  );
    107 
    108  // Check that none of the above ran proxy traps.
    109  strictEqual(global.trapDidRun, false, "No proxy trap did run.");
    110 
    111  // Resume the debugger and finish the current test.
    112  await threadFront.resume();
    113 
    114  function eval_code() {
    115    // Create objects in `global`, and debug them in `debuggee`. They may get various
    116    // kinds of security wrappers, or no wrapper at all.
    117    // To detect that no proxy trap runs, the proxy handler should define all possible
    118    // traps, but the list is long and may change. Therefore a second proxy is used as
    119    // the handler, so that a single `get` trap suffices.
    120    global.eval(`
    121      var trapDidRun = false;
    122      var proxy = new Proxy({}, new Proxy({}, {get: (_, trap) => {
    123        trapDidRun = true;
    124        throw new Error("proxy trap '" + trap + "' was called.");
    125      }}));
    126      var inheritsProxy = Object.create(proxy, {x:{value:1}});
    127    `);
    128    const data = Cu.createObjectIn(debuggee, { defineAs: "data" });
    129    data.proxy = global.proxy;
    130    data.inheritsProxy = global.inheritsProxy;
    131    debuggee.eval(`
    132      var inheritsProxy2 = Object.create(data.proxy, {x:{value:1}});
    133      stopMe(data.proxy, data.inheritsProxy, inheritsProxy2);
    134    `);
    135  }
    136 }
    137 
    138 function check_proxy_grip(debuggee, testOptions, grip) {
    139  const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
    140  const { preview } = grip;
    141 
    142  if (global === debuggee) {
    143    // The proxy has no security wrappers.
    144    strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
    145    strictEqual(
    146      preview.ownPropertiesLength,
    147      2,
    148      "The preview has 2 properties."
    149    );
    150    const props = preview.ownProperties;
    151    ok(props["<target>"].value, "<target> contains the [[ProxyTarget]].");
    152    ok(props["<handler>"].value, "<handler> contains the [[ProxyHandler]].");
    153  } else if (isOpaque) {
    154    // The proxy has opaque security wrappers.
    155    strictEqual(grip.class, "Opaque", "The grip has an Opaque class.");
    156    strictEqual(grip.ownPropertyLength, 0, "The grip has no properties.");
    157  } else if (!subsumes) {
    158    // The proxy belongs to compartment not subsumed by the debuggee.
    159    strictEqual(grip.class, "Restricted", "The grip has a Restricted class.");
    160    strictEqual(
    161      grip.ownPropertyLength,
    162      undefined,
    163      "The grip doesn't know the number of properties."
    164    );
    165  } else if (globalIsInvisible) {
    166    // The proxy belongs to an invisible-to-debugger compartment.
    167    strictEqual(
    168      grip.class,
    169      "InvisibleToDebugger: Object",
    170      "The grip has an InvisibleToDebugger class."
    171    );
    172    ok(
    173      !("ownPropertyLength" in grip),
    174      "The grip doesn't know the number of properties."
    175    );
    176  } else {
    177    // The proxy has non-opaque security wrappers.
    178    strictEqual(grip.class, "Proxy", "The grip has a Proxy class.");
    179    strictEqual(
    180      preview.ownPropertiesLength,
    181      0,
    182      "The preview has no properties."
    183    );
    184    ok(!("<target>" in preview), "The preview has no <target> property.");
    185    ok(!("<handler>" in preview), "The preview has no <handler> property.");
    186  }
    187 }
    188 
    189 function check_proxy_slots(debuggee, testOptions, grip, proxySlots) {
    190  const { global } = testOptions;
    191 
    192  if (grip.class !== "Proxy") {
    193    strictEqual(
    194      proxySlots,
    195      null,
    196      "Slots can only be retrived for Proxy grips."
    197    );
    198  } else if (global === debuggee) {
    199    const { proxyTarget, proxyHandler } = proxySlots;
    200    strictEqual(
    201      proxyTarget.getGrip().type,
    202      "object",
    203      "There is a [[ProxyTarget]] grip."
    204    );
    205    strictEqual(
    206      proxyHandler.getGrip().type,
    207      "object",
    208      "There is a [[ProxyHandler]] grip."
    209    );
    210  } else {
    211    const { proxyTarget, proxyHandler } = proxySlots;
    212    strictEqual(
    213      proxyTarget.type,
    214      "undefined",
    215      "There is no [[ProxyTarget]] grip."
    216    );
    217    strictEqual(
    218      proxyHandler.type,
    219      "undefined",
    220      "There is no [[ProxyHandler]] grip."
    221    );
    222  }
    223 }
    224 
    225 function check_properties(testOptions, props, isProxy, createdInDebuggee) {
    226  const { subsumes, globalIsInvisible } = testOptions;
    227  const ownPropertiesLength = Reflect.ownKeys(props).length;
    228 
    229  if (createdInDebuggee || (!isProxy && subsumes && !globalIsInvisible)) {
    230    // The debuggee can access the properties.
    231    strictEqual(ownPropertiesLength, 1, "1 own property was retrieved.");
    232    strictEqual(props.x.value, 1, "The property has the right value.");
    233  } else {
    234    // The debuggee is not allowed to access the object.
    235    strictEqual(ownPropertiesLength, 0, "No own property could be retrieved.");
    236  }
    237 }
    238 
    239 function check_prototype(
    240  debuggee,
    241  testOptions,
    242  proto,
    243  isProxy,
    244  createdInDebuggee
    245 ) {
    246  const { global, isOpaque, subsumes, globalIsInvisible } = testOptions;
    247  if (isOpaque && !globalIsInvisible && !createdInDebuggee) {
    248    // The object is or inherits from a proxy with opaque security wrappers.
    249    // The debuggee sees `Object.prototype` when retrieving the prototype.
    250    strictEqual(
    251      proto.getGrip().class,
    252      "Object",
    253      "The prototype has a Object class."
    254    );
    255  } else if (isProxy && isOpaque && globalIsInvisible) {
    256    // The object is a proxy with opaque security wrappers in an invisible global.
    257    // The debuggee sees an inaccessible `Object.prototype` when retrieving the prototype.
    258    strictEqual(
    259      proto.getGrip().class,
    260      "InvisibleToDebugger: Object",
    261      "The prototype has an InvisibleToDebugger class."
    262    );
    263  } else if (
    264    createdInDebuggee ||
    265    (!isProxy && subsumes && !globalIsInvisible)
    266  ) {
    267    // The object inherits from a proxy and has no security wrappers or non-opaque ones.
    268    // The debuggee sees the proxy when retrieving the prototype.
    269    check_proxy_grip(
    270      debuggee,
    271      { global, isOpaque, subsumes, globalIsInvisible },
    272      proto.getGrip()
    273    );
    274  } else {
    275    // The debuggee is not allowed to access the object. It sees a null prototype.
    276    strictEqual(proto.type, "null", "The prototype is null.");
    277  }
    278 }
    279 
    280 function createNullPrincipal() {
    281  return Services.scriptSecurityManager.createNullPrincipal({});
    282 }
    283 
    284 async function run_tests_in_principal(
    285  options,
    286  debuggeePrincipal,
    287  debuggeeHasXrays
    288 ) {
    289  const { debuggee } = options;
    290  debuggee.eval(
    291    // These arguments are tested.
    292    // eslint-disable-next-line no-unused-vars
    293    function stopMe(arg1, arg2) {
    294      debugger;
    295    }.toString()
    296  );
    297 
    298  // Test objects created in the debuggee.
    299  await testPrincipal(options, undefined, debuggeeHasXrays);
    300 
    301  // Test objects created in a system principal new global.
    302  await testPrincipal(options, systemPrincipal, debuggeeHasXrays);
    303 
    304  // Test objects created in a cross-origin null principal new global.
    305  await testPrincipal(options, createNullPrincipal(), debuggeeHasXrays);
    306 
    307  if (debuggeePrincipal != systemPrincipal) {
    308    // Test objects created in a same-origin principal new global.
    309    await testPrincipal(options, debuggeePrincipal, debuggeeHasXrays);
    310  }
    311 }
    312 
    313 for (const principal of [systemPrincipal, createNullPrincipal()]) {
    314  for (const wantXrays of [true, false]) {
    315    add_task(
    316      threadFrontTest(
    317        options => run_tests_in_principal(options, principal, wantXrays),
    318        { principal, wantXrays }
    319      )
    320    );
    321  }
    322 }