summaryrefslogtreecommitdiffstats
path: root/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js
blob: 62d57533fad2eb1b40a42571251c69d08c284587 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// Test that it's not possible to create expando properties on XPCWNs.
// See <https://bugzilla.mozilla.org/show_bug.cgi?id=1143810#c5>.

function TestInterfaceAll() {}
TestInterfaceAll.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIXPCTestInterfaceA",
                                          "nsIXPCTestInterfaceB",
                                          "nsIXPCTestInterfaceC"]),

  /* nsIXPCTestInterfaceA / nsIXPCTestInterfaceB */
  name: "TestInterfaceAllDefaultName",

  /* nsIXPCTestInterfaceC */
  someInteger: 42
};

function check_throws(f) {
  try {
    f();
  } catch (exc) {
    return;
  }
  throw new TypeError("Expected exception, no exception thrown");
}

/*
 * Test that XPCWrappedNative objects do not permit expando properties to be created.
 *
 * This function is called twice. The first time, realObj is an nsITimer XPCWN
 * and accessObj === realObj.
 *
 * The second time, accessObj is a scripted proxy with realObj as its target.
 * So the second time we are testing that scripted proxies don't magically
 * bypass whatever mechanism enforces the expando policy on XPCWNs.
 */
function test_tamperproof(realObj, accessObj, {method, constant, attribute}) {
  // Assignment can't create an expando property.
  check_throws(function () { accessObj.expando = 14; });
  Assert.equal(false, "expando" in realObj);

  // Strict assignment throws.
  check_throws(function () { "use strict"; accessObj.expando = 14; });
  Assert.equal(false, "expando" in realObj);

  // Assignment to an inherited method name doesn't work either.
  check_throws(function () { accessObj.hasOwnProperty = () => "lies"; });
  check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; });
  Assert.ok(!realObj.hasOwnProperty("hasOwnProperty"));

  // Assignment to a method name doesn't work either.
  let originalMethod;
  if (method) {
    originalMethod = accessObj[method];
    accessObj[method] = "nope";  // non-writable data property, no exception in non-strict code
    check_throws(function () { "use strict"; accessObj[method] = "nope"; });
    Assert.ok(realObj[method] === originalMethod);
  }

  // A constant is the same thing.
  let originalConstantValue;
  if (constant) {
    originalConstantValue = accessObj[constant];
    accessObj[constant] = "nope";
    Assert.equal(realObj[constant], originalConstantValue);
    check_throws(function () { "use strict"; accessObj[constant] = "nope"; });
    Assert.equal(realObj[constant], originalConstantValue);
  }

  // Assignment to a readonly accessor property with no setter doesn't work either.
  let originalAttributeDesc;
  if (attribute) {
    originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute);
    Assert.ok("set" in originalAttributeDesc);
    Assert.ok(originalAttributeDesc.set === undefined);

    accessObj[attribute] = "nope";  // accessor property with no setter: no exception in non-strict code
    check_throws(function () { "use strict"; accessObj[attribute] = "nope"; });

    let desc = Object.getOwnPropertyDescriptor(realObj, attribute);
    Assert.ok("set" in desc);
    Assert.equal(originalAttributeDesc.get, desc.get);
    Assert.equal(undefined, desc.set);
  }

  // Reflect.set doesn't work either.
  if (method) {
    Assert.ok(!Reflect.set({}, method, "bad", accessObj));
    Assert.equal(realObj[method], originalMethod);
  }
  if (attribute) {
    Assert.ok(!Reflect.set({}, attribute, "bad", accessObj));
    Assert.equal(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get);
  }

  // Object.defineProperty can't do anything either.
  let names = ["expando"];
  if (method) names.push(method);
  if (constant) names.push(constant);
  if (attribute) names.push(attribute);
  for (let name of names) {
    let originalDesc = Object.getOwnPropertyDescriptor(realObj, name);
    check_throws(function () {
      Object.defineProperty(accessObj, name, {configurable: true});
    });
    check_throws(function () {
      Object.defineProperty(accessObj, name, {writable: true});
    });
    check_throws(function () {
      Object.defineProperty(accessObj, name, {get: function () { return "lies"; }});
    });
    check_throws(function () {
      Object.defineProperty(accessObj, name, {value: "bad"});
    });
    let desc = Object.getOwnPropertyDescriptor(realObj, name);
    if (originalDesc === undefined) {
      Assert.equal(undefined, desc);
    } else {
      Assert.equal(originalDesc.configurable, desc.configurable);
      Assert.equal(originalDesc.enumerable, desc.enumerable);
      Assert.equal(originalDesc.writable, desc.writable);
      Assert.equal(originalDesc.value, desc.value);
      Assert.equal(originalDesc.get, desc.get);
      Assert.equal(originalDesc.set, desc.set);
    }
  }

  // Existing properties can't be deleted.
  if (method) {
    Assert.equal(false, delete accessObj[method]);
    check_throws(function () { "use strict"; delete accessObj[method]; });
    Assert.equal(realObj[method], originalMethod);
  }
  if (constant) {
    Assert.equal(false, delete accessObj[constant]);
    check_throws(function () { "use strict"; delete accessObj[constant]; });
    Assert.equal(realObj[constant], originalConstantValue);
  }
  if (attribute) {
    Assert.equal(false, delete accessObj[attribute]);
    check_throws(function () { "use strict"; delete accessObj[attribute]; });
    desc = Object.getOwnPropertyDescriptor(realObj, attribute);
    Assert.equal(originalAttributeDesc.get, desc.get);
  }
}

function test_twice(obj, options) {
  test_tamperproof(obj, obj, options);

  let handler = {
    getPrototypeOf(t) {
      return new Proxy(Object.getPrototypeOf(t), handler);
    }
  };
  test_tamperproof(obj, new Proxy(obj, handler), options);
}

function run_test() {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  test_twice(timer, {
    method: "init",
    constant: "TYPE_ONE_SHOT",
    attribute: "callback"
  });

  let cmdline = Cu.createCommandLine([], null, Ci.nsICommandLine.STATE_INITIAL_LAUNCH);
  test_twice(cmdline, {});

  test_twice(Object.getPrototypeOf(cmdline), {
    method: "getArgument",
    constant: "STATE_INITIAL_LAUNCH",
    attribute: "length"
  });

  // Test a tearoff object.
  let b = xpcWrap(new TestInterfaceAll(), Ci.nsIXPCTestInterfaceB);
  let tearoff = b.nsIXPCTestInterfaceA;
  test_twice(tearoff, {
    method: "QueryInterface"
  });
}