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 );