diff options
Diffstat (limited to '')
-rw-r--r-- | devtools/server/tests/xpcshell/test_objectgrips-21.js | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/devtools/server/tests/xpcshell/test_objectgrips-21.js b/devtools/server/tests/xpcshell/test_objectgrips-21.js new file mode 100644 index 0000000000..cfb4f7486f --- /dev/null +++ b/devtools/server/tests/xpcshell/test_objectgrips-21.js @@ -0,0 +1,398 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true); + +registerCleanupFunction(() => { + Services.prefs.clearUserPref("security.allow_eval_with_system_principal"); +}); + +// Run test_unsafe_grips twice, one against a system principal debuggee +// and another time with a null principal debuggee + +// The following tests work like this: +// - The specified code is evaluated in a system principal. +// `Cu`, `systemPrincipal` and `Services` are provided as global variables. +// - The resulting object is debugged in a system or null principal debuggee, +// depending on in which list the test is placed. +// It is tested according to the specified test parameters. +// - An ordinary object that inherits from the resulting one is also debugged. +// This is just to check that it can be normally debugged even with an unsafe +// object in the prototype. The specified test parameters do not apply. + +// The following tests are defined via properties with the following defaults. +const defaults = { + // The class of the grip. + class: "Restricted", + + // The stringification of the object + string: "", + + // Whether the object (not its grip) has class "Function". + isFunction: false, + + // Whether the grip has a preview property. + hasPreview: true, + + // Code that assigns the object to be tested into the obj variable. + code: "var obj = {}", + + // The type of the grip of the prototype. + protoType: "null", + + // Whether the object has some own string properties. + hasOwnPropertyNames: false, + + // Whether the object has some own symbol properties. + hasOwnPropertySymbols: false, + + // The descriptor obtained when retrieving property "x" or Symbol("x"). + property: undefined, + + // Code evaluated after the test, whose result is expected to be true. + afterTest: "true == true", +}; + +// The following tests use a system principal debuggee. +const systemPrincipalTests = [ + { + // Dead objects throw a TypeError when accessing properties. + class: "DeadObject", + string: "<dead object>", + code: ` + var obj = Cu.Sandbox(null); + Cu.nukeSandbox(obj); + `, + property: descriptor({ value: "TypeError" }), + }, + { + // This proxy checks that no trap runs (using a second proxy as the handler + // there is no need to maintain a list of all possible traps). + class: "Proxy", + string: "<proxy>", + code: ` + var trapDidRun = false; + var obj = new Proxy({}, new Proxy({}, {get: (_, trap) => { + trapDidRun = true; + throw new Error("proxy trap '" + trap + "' was called."); + }})); + `, + afterTest: "trapDidRun === false", + }, + { + // Like the previous test, but now the proxy has a Function class. + class: "Proxy", + string: "<proxy>", + isFunction: true, + code: ` + var trapDidRun = false; + var obj = new Proxy(function(){}, new Proxy({}, {get: (_, trap) => { + trapDidRun = true; + throw new Error("proxy trap '" + trap + "' was called.(function)"); + }})); + `, + afterTest: "trapDidRun === false", + }, + { + // Invisisible-to-debugger objects can't be unwrapped, so we don't know if + // they are proxies. Thus they shouldn't be accessed. + class: "InvisibleToDebugger: Array", + string: "<invisibleToDebugger>", + hasPreview: false, + code: ` + var s = Cu.Sandbox(systemPrincipal, {invisibleToDebugger: true}); + var obj = s.eval("[1, 2, 3]"); + `, + }, + { + // Like the previous test, but now the object has a Function class. + class: "InvisibleToDebugger: Function", + string: "<invisibleToDebugger>", + isFunction: true, + hasPreview: false, + code: ` + var s = Cu.Sandbox(systemPrincipal, {invisibleToDebugger: true}); + var obj = s.eval("(function func(arg){})"); + `, + }, + { + // Cu.Sandbox is a WrappedNative that throws when accessing properties. + class: "nsXPCComponents_utils_Sandbox", + string: "[object nsXPCComponents_utils_Sandbox]", + code: `var obj = Cu.Sandbox;`, + protoType: "object", + }, +]; + +// The following tests run code in a system principal, but the resulting object +// is debugged in a null principal. +const nullPrincipalTests = [ + { + // The null principal gets undefined when attempting to access properties. + string: "[object Object]", + code: `var obj = {x: -1};`, + }, + { + // For arrays it's an error instead of undefined. + string: "[object Object]", + code: `var obj = [1, 2, 3];`, + property: descriptor({ value: "Error" }), + }, + { + // For functions it's also an error. + string: "function func(arg){}", + isFunction: true, + hasPreview: false, + code: `var obj = function func(arg){};`, + property: descriptor({ value: "Error" }), + }, + { + // Check that no proxy trap runs. + string: "[object Object]", + code: ` + var trapDidRun = false; + var obj = new Proxy([], new Proxy({}, {get: (_, trap) => { + trapDidRun = true; + throw new Error("proxy trap '" + trap + "' was called."); + }})); + `, + property: descriptor({ value: "Error" }), + afterTest: `trapDidRun === false`, + }, + { + // Like the previous test, but now the object is a callable Proxy. + string: "function () {\n [native code]\n}", + isFunction: true, + hasPreview: false, + code: ` + var trapDidRun = false; + var obj = new Proxy(function(){}, new Proxy({}, {get: (_, trap) => { + trapDidRun = true; + throw new Error("proxy trap '" + trap + "' was called."); + }})); + `, + property: descriptor({ value: "Error" }), + afterTest: `trapDidRun === false`, + }, + { + // Cross-origin Window objects do expose some properties and have a preview. + string: "[object Object]", + code: `var obj = Services.appShell.createWindowlessBrowser().document.defaultView;`, + hasOwnPropertyNames: true, + hasOwnPropertySymbols: true, + property: descriptor({ value: "SecurityError" }), + previewUrl: "about:blank", + }, + { + // Cross-origin Location objects do expose some properties and have a preview. + string: "[object Object]", + code: `var obj = Services.appShell.createWindowlessBrowser().document.defaultView + .location;`, + hasOwnPropertyNames: true, + hasOwnPropertySymbols: true, + property: descriptor({ value: "SecurityError" }), + }, +]; + +function descriptor(descr) { + return Object.assign( + { + configurable: false, + writable: false, + enumerable: false, + value: undefined, + }, + descr + ); +} + +async function test_unsafe_grips( + { threadFront, debuggee, isWorkerServer }, + tests +) { + debuggee.eval( + function stopMe(arg1, arg2) { + debugger; + }.toString() + ); + + for (let data of tests) { + data = { ...defaults, ...data }; + + // Run the code and test the results. + const sandbox = Cu.Sandbox(systemPrincipal); + Object.assign(sandbox, { Services, systemPrincipal, Cu }); + sandbox.eval(data.code); + debuggee.obj = sandbox.obj; + const inherits = `Object.create(obj, { + x: {value: 1}, + [Symbol.for("x")]: {value: 2} + })`; + + const packet = await executeOnNextTickAndWaitForPause( + () => debuggee.eval(`stopMe(obj, ${inherits});`), + threadFront + ); + + const [objGrip, inheritsGrip] = packet.frame.arguments; + for (const grip of [objGrip, inheritsGrip]) { + const isUnsafe = grip === objGrip; + // If `isUnsafe` is true, the parameters in `data` will be used to assert + // against `objGrip`, the grip of the object `obj` created by the test. + // Otherwise, the grip will refer to `inherits`, an ordinary object which + // inherits from `obj`. Then all checks are hardcoded because in every test + // all methods are expected to work the same on `inheritsGrip`. + check_grip(grip, data, isUnsafe, isWorkerServer); + + const objClient = threadFront.pauseGrip(grip); + let response, slice; + + response = await objClient.getPrototypeAndProperties(); + check_properties(response.ownProperties, data, isUnsafe); + check_symbols(response.ownSymbols, data, isUnsafe); + check_prototype(response.prototype, data, isUnsafe, isWorkerServer); + + response = await objClient.enumProperties({ + ignoreIndexedProperties: true, + }); + slice = await response.slice(0, response.count); + check_properties(slice.ownProperties, data, isUnsafe); + + response = await objClient.enumProperties({}); + slice = await response.slice(0, response.count); + check_properties(slice.ownProperties, data, isUnsafe); + + response = await objClient.getProperty("x"); + check_property(response.descriptor, data, isUnsafe); + + response = await objClient.enumSymbols(); + slice = await response.slice(0, response.count); + check_symbol_names(slice.ownSymbols, data, isUnsafe); + + response = await objClient.getProperty(Symbol.for("x")); + check_symbol(response.descriptor, data, isUnsafe); + + response = await objClient.getPrototype(); + check_prototype(response.prototype, data, isUnsafe, isWorkerServer); + + await objClient.release(); + } + + await threadFront.resume(); + + ok(sandbox.eval(data.afterTest), "Check after test passes"); + } +} + +function check_grip(grip, data, isUnsafe, isWorkerServer) { + if (isUnsafe) { + strictEqual(grip.class, data.class, "The grip has the proper class."); + strictEqual("preview" in grip, data.hasPreview, "Check preview presence."); + // preview.url isn't populated on worker server. + if (data.previewUrl && !isWorkerServer) { + console.trace(); + strictEqual( + grip.preview.url, + data.previewUrl, + `Check preview.url for "${data.code}".` + ); + } + } else { + strictEqual(grip.class, "Object", "The grip has 'Object' class."); + ok("preview" in grip, "The grip has a preview."); + } +} + +function check_properties(props, data, isUnsafe) { + const propNames = Reflect.ownKeys(props); + check_property_names(propNames, data, isUnsafe); + if (isUnsafe) { + deepEqual(props.x, undefined, "The property does not exist."); + } else { + strictEqual(props.x.value, 1, "The property has the right value."); + } +} + +function check_property_names(props, data, isUnsafe) { + if (isUnsafe) { + strictEqual( + !!props.length, + data.hasOwnPropertyNames, + "Check presence of own string properties." + ); + } else { + strictEqual(props.length, 1, "1 own property was retrieved."); + strictEqual(props[0], "x", "The property has the right name."); + } +} + +function check_property(descr, data, isUnsafe) { + if (isUnsafe) { + deepEqual(descr, data.property, "Got the right property descriptor."); + } else { + strictEqual(descr.value, 1, "The property has the right value."); + } +} + +function check_symbols(symbols, data, isUnsafe) { + check_symbol_names(symbols, data, isUnsafe); + if (!isUnsafe) { + check_symbol(symbols[0].descriptor, data, isUnsafe); + } +} + +function check_symbol_names(props, data, isUnsafe) { + if (isUnsafe) { + strictEqual( + !!props.length, + data.hasOwnPropertySymbols, + "Check presence of own symbol properties." + ); + } else { + strictEqual(props.length, 1, "1 own symbol property was retrieved."); + strictEqual(props[0].name, "Symbol(x)", "The symbol has the right name."); + } +} + +function check_symbol(descr, data, isUnsafe) { + if (isUnsafe) { + deepEqual( + descr, + data.property, + "Got the right symbol property descriptor." + ); + } else { + strictEqual(descr.value, 2, "The symbol property has the right value."); + } +} + +function check_prototype(proto, data, isUnsafe, isWorkerServer) { + const protoGrip = proto && proto.getGrip ? proto.getGrip() : proto; + if (isUnsafe) { + deepEqual(protoGrip.type, data.protoType, "Got the right prototype type."); + } else { + check_grip(protoGrip, data, true, isWorkerServer); + } +} + +// threadFrontTest uses systemPrincipal by default, but let's be explicit here. +add_task( + threadFrontTest( + options => { + return test_unsafe_grips(options, systemPrincipalTests, "system"); + }, + { principal: systemPrincipal } + ) +); + +const nullPrincipal = Services.scriptSecurityManager.createNullPrincipal({}); +add_task( + threadFrontTest( + options => { + return test_unsafe_grips(options, nullPrincipalTests, "null"); + }, + { principal: nullPrincipal } + ) +); |