tor-browser

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

test_xpcwn_tamperproof.js (6425B)


      1 // Test that it's not possible to create expando properties on XPCWNs.
      2 // See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>.
      3 
      4 function TestInterfaceAll() {}
      5 TestInterfaceAll.prototype = {
      6  QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA",
      7                                          "nsIXPCTestInterfaceB",
      8                                          "nsIXPCTestInterfaceC"]),
      9 
     10  /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */
     11  name: "TestInterfaceAllDefaultName",
     12 
     13  /* nsIXPCTestInterfaceC */
     14  someInteger: 42
     15 };
     16 
     17 function check_throws(f) {
     18  try {
     19    f();
     20  } catch (exc) {
     21    return;
     22  }
     23  throw new TypeError("Expected exception, no exception thrown");
     24 }
     25 
     26 /*
     27 * Test that XPCWrappedNative objects do not permit expando properties to be created.
     28 *
     29 * This function is called twice. The first time, realObj is an nsITimer XPCWN
     30 * and accessObj === realObj.
     31 *
     32 * The second time, accessObj is a scripted proxy with realObj as its target.
     33 * So the second time we are testing that scripted proxies don't magically
     34 * bypass whatever mechanism enforces the expando policy on XPCWNs.
     35 */
     36 function test_tamperproof(realObj, accessObj, {method, constant, attribute}) {
     37  // Assignment can't create an expando property.
     38  check_throws(function () { accessObj.expando = 14; });
     39  Assert.equal(false, "expando" in realObj);
     40 
     41  // Strict assignment throws.
     42  check_throws(function () { "use strict"; accessObj.expando = 14; });
     43  Assert.equal(false, "expando" in realObj);
     44 
     45  // Assignment to an inherited method name doesn't work either.
     46  check_throws(function () { accessObj.hasOwnProperty = () => "lies"; });
     47  check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; });
     48  Assert.ok(!realObj.hasOwnProperty("hasOwnProperty"));
     49 
     50  // Assignment to a method name doesn't work either.
     51  let originalMethod;
     52  if (method) {
     53    originalMethod = accessObj[method];
     54    accessObj[method] = "nope";  // non-writable data property, no exception in non-strict code
     55    check_throws(function () { "use strict"; accessObj[method] = "nope"; });
     56    Assert.ok(realObj[method] === originalMethod);
     57  }
     58 
     59  // A constant is the same thing.
     60  let originalConstantValue;
     61  if (constant) {
     62    originalConstantValue = accessObj[constant];
     63    accessObj[constant] = "nope";
     64    Assert.equal(realObj[constant], originalConstantValue);
     65    check_throws(function () { "use strict"; accessObj[constant] = "nope"; });
     66    Assert.equal(realObj[constant], originalConstantValue);
     67  }
     68 
     69  // Assignment to a readonly accessor property with no setter doesn't work either.
     70  let originalAttributeDesc;
     71  if (attribute) {
     72    originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute);
     73    Assert.ok("set" in originalAttributeDesc);
     74    Assert.ok(originalAttributeDesc.set === undefined);
     75 
     76    accessObj[attribute] = "nope";  // accessor property with no setter: no exception in non-strict code
     77    check_throws(function () { "use strict"; accessObj[attribute] = "nope"; });
     78 
     79    let desc = Object.getOwnPropertyDescriptor(realObj, attribute);
     80    Assert.ok("set" in desc);
     81    Assert.equal(originalAttributeDesc.get, desc.get);
     82    Assert.equal(undefined, desc.set);
     83  }
     84 
     85  // Reflect.set doesn't work either.
     86  if (method) {
     87    Assert.ok(!Reflect.set({}, method, "bad", accessObj));
     88    Assert.equal(realObj[method], originalMethod);
     89  }
     90  if (attribute) {
     91    Assert.ok(!Reflect.set({}, attribute, "bad", accessObj));
     92    Assert.equal(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get);
     93  }
     94 
     95  // Object.defineProperty can't do anything either.
     96  let names = ["expando"];
     97  if (method) names.push(method);
     98  if (constant) names.push(constant);
     99  if (attribute) names.push(attribute);
    100  for (let name of names) {
    101    let originalDesc = Object.getOwnPropertyDescriptor(realObj, name);
    102    check_throws(function () {
    103      Object.defineProperty(accessObj, name, {configurable: true});
    104    });
    105    check_throws(function () {
    106      Object.defineProperty(accessObj, name, {writable: true});
    107    });
    108    check_throws(function () {
    109      Object.defineProperty(accessObj, name, {get: function () { return "lies"; }});
    110    });
    111    check_throws(function () {
    112      Object.defineProperty(accessObj, name, {value: "bad"});
    113    });
    114    let desc = Object.getOwnPropertyDescriptor(realObj, name);
    115    if (originalDesc === undefined) {
    116      Assert.equal(undefined, desc);
    117    } else {
    118      Assert.equal(originalDesc.configurable, desc.configurable);
    119      Assert.equal(originalDesc.enumerable, desc.enumerable);
    120      Assert.equal(originalDesc.writable, desc.writable);
    121      Assert.equal(originalDesc.value, desc.value);
    122      Assert.equal(originalDesc.get, desc.get);
    123      Assert.equal(originalDesc.set, desc.set);
    124    }
    125  }
    126 
    127  // Existing properties can't be deleted.
    128  if (method) {
    129    Assert.equal(false, delete accessObj[method]);
    130    check_throws(function () { "use strict"; delete accessObj[method]; });
    131    Assert.equal(realObj[method], originalMethod);
    132  }
    133  if (constant) {
    134    Assert.equal(false, delete accessObj[constant]);
    135    check_throws(function () { "use strict"; delete accessObj[constant]; });
    136    Assert.equal(realObj[constant], originalConstantValue);
    137  }
    138  if (attribute) {
    139    Assert.equal(false, delete accessObj[attribute]);
    140    check_throws(function () { "use strict"; delete accessObj[attribute]; });
    141    desc = Object.getOwnPropertyDescriptor(realObj, attribute);
    142    Assert.equal(originalAttributeDesc.get, desc.get);
    143  }
    144 }
    145 
    146 function test_twice(obj, options) {
    147  test_tamperproof(obj, obj, options);
    148 
    149  let handler = {
    150    getPrototypeOf(t) {
    151      return new Proxy(Object.getPrototypeOf(t), handler);
    152    }
    153  };
    154  test_tamperproof(obj, new Proxy(obj, handler), options);
    155 }
    156 
    157 function run_test() {
    158  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    159  test_twice(timer, {
    160    method: "init",
    161    constant: "TYPE_ONE_SHOT",
    162    attribute: "callback"
    163  });
    164 
    165  let cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH);
    166  test_twice(cmdline, {});
    167 
    168  test_twice(Object.getPrototypeOf(cmdline), {
    169    method: "getArgument",
    170    constant: "STATE_INITIAL_LAUNCH",
    171    attribute: "length"
    172  });
    173 
    174  // Test a tearoff object.
    175  let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB);
    176  let tearoff = b.nsIXPCTestInterfaceA;
    177  test_twice(tearoff, {
    178    method: "QueryInterface"
    179  });
    180 }