Bug 1149618 - Add a sandbox parameter to execute, r=dburns

This commit is contained in:
Jonathan Griffin 2015-04-23 13:39:38 -07:00
parent 9f386bdf5a
commit a779e43382
5 changed files with 171 additions and 61 deletions

View File

@ -0,0 +1,72 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from marionette import MarionetteTestCase
from marionette_driver.errors import JavascriptException
class TestExecuteSandboxes(MarionetteTestCase):
def setUp(self):
super(TestExecuteSandboxes, self).setUp()
def test_execute_system_sandbox(self):
# Test that 'system' sandbox has elevated privileges in execute_script
result = self.marionette.execute_script("""
return Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
""", sandbox='system')
self.assertEqual(result, 1)
def test_execute_async_system_sandbox(self):
# Test that 'system' sandbox has elevated privileges in
# execute_async_script.
result = self.marionette.execute_async_script("""
let result = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
marionetteScriptFinished(result);
""", sandbox='system')
self.assertEqual(result, 1)
def test_execute_switch_sandboxes(self):
# Test that sandboxes are retained when switching between them
# for execute_script.
self.marionette.execute_script("foo = 1;", sandbox='1')
self.marionette.execute_script("foo = 2;", sandbox='2')
foo = self.marionette.execute_script("return foo;", sandbox='1',
new_sandbox=False)
self.assertEqual(foo, 1)
foo = self.marionette.execute_script("return foo;", sandbox='2',
new_sandbox=False)
self.assertEqual(foo, 2)
def test_execute_new_sandbox(self):
# Test that clearing a sandbox does not affect other sandboxes
self.marionette.execute_script("foo = 1;", sandbox='1')
self.marionette.execute_script("foo = 2;", sandbox='2')
self.assertRaises(JavascriptException,
self.marionette.execute_script,
"return foo;", sandbox='1', new_sandbox=True)
foo = self.marionette.execute_script("return foo;", sandbox='2',
new_sandbox=False)
self.assertEqual(foo, 2)
def test_execute_async_switch_sandboxes(self):
# Test that sandboxes are retained when switching between them
# for execute_async_script.
self.marionette.execute_async_script("foo = 1; marionetteScriptFinished()",
sandbox='1')
self.marionette.execute_async_script("foo = 2; marionetteScriptFinished()",
sandbox='2')
foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
sandbox='1',
new_sandbox=False)
self.assertEqual(foo, 1)
foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
sandbox='2',
new_sandbox=False)
self.assertEqual(foo, 2)
class TestExecuteSandboxesChrome(TestExecuteSandboxes):
def setUp(self):
super(TestExecuteSandboxesChrome, self).setUp()
self.marionette.set_context("chrome")

View File

@ -156,3 +156,5 @@ b2g = false
[test_file_upload.py]
b2g = false
skip-if = os == "win" # http://bugs.python.org/issue14574
[test_execute_sandboxes.py]

View File

@ -131,7 +131,7 @@ this.GeckoDriver = function(appName, device, emulator) {
this.testName = null;
this.mozBrowserClose = null;
this.enabled_security_pref = false;
this.sandbox = null;
this.sandboxes = {};
// frame ID of the current remote frame, used for mozbrowserclose events
this.oopFrameId = null;
this.observing = null;
@ -715,11 +715,17 @@ GeckoDriver.prototype.getContext = function(cmd, resp) {
* @return {nsIXPCComponents_utils_Sandbox}
* Returns the sandbox.
*/
GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sp) {
let sb = new Cu.Sandbox(win,
GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sp, sandboxName) {
let principal = win;
if (sandboxName == 'system') {
principal = Cc["@mozilla.org/systemprincipal;1"].
createInstance(Ci.nsIPrincipal);
}
let sb = new Cu.Sandbox(principal,
{sandboxPrototype: win, wantXrays: false, sandboxName: ""});
sb.global = sb;
sb.testUtils = utils;
sb.proto = win;
mn.exports.forEach(function(fn) {
if (typeof mn[fn] === 'function') {
@ -740,7 +746,7 @@ GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sp) {
pow.map(s => loader.loadSubScript(s, sb));
}
return sb;
this.sandboxes[sandboxName] = sb;
};
/**
@ -820,6 +826,7 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
specialPowers,
filename,
line} = cmd.parameters;
let sandboxName = cmd.parameters.sandbox || 'default';
if (!scriptTimeout) {
scriptTimeout = this.scriptTimeout;
@ -836,7 +843,8 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
timeout: scriptTimeout,
specialPowers: specialPowers,
filename: filename,
line: line
line: line,
sandboxName: sandboxName
});
return;
}
@ -860,7 +868,9 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
}
let win = this.getCurrentWindow();
if (!this.sandbox || newSandbox) {
if (newSandbox ||
!(sandboxName in this.sandboxes) ||
(this.sandboxes[sandboxName].proto != win)) {
let marionette = new Marionette(
this,
win,
@ -869,22 +879,23 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
scriptTimeout,
this.heartbeatCallback,
this.testName);
this.sandbox = this.createExecuteSandbox(
this.createExecuteSandbox(
win,
marionette,
specialPowers);
if (!this.sandbox) {
specialPowers,
sandboxName);
if (!this.sandboxes[sandboxName]) {
return;
}
}
this.applyArgumentsToSandbox(win, this.sandbox, args);
this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
try {
this.sandbox.finish = () => {
this.sandboxes[sandboxName].finish = () => {
if (this.inactivityTimer !== null) {
this.inactivityTimer.cancel();
}
return this.sandbox.generate_results();
return this.sandboxes[sandboxName].generate_results();
};
if (!directInject) {
@ -892,7 +903,7 @@ GeckoDriver.prototype.execute = function(cmd, resp, directInject) {
}
this.executeScriptInSandbox(
resp,
this.sandbox,
this.sandboxes[sandboxName],
script,
directInject,
false /* async */,
@ -951,6 +962,7 @@ GeckoDriver.prototype.executeJSScript = function(cmd, resp) {
specialPowers: cmd.parameters.specialPowers,
filename: cmd.parameters.filename,
line: cmd.parameters.line,
sandboxName: cmd.parameters.sandbox || 'default',
});
break;
}
@ -980,6 +992,7 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
specialPowers,
filename,
line} = cmd.parameters;
let sandboxName = cmd.parameters.sandbox || 'default';
if (!scriptTimeout) {
scriptTimeout = this.scriptTimeout;
@ -998,7 +1011,8 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
inactivityTimeout: inactivityTimeout,
specialPowers: specialPowers,
filename: filename,
line: line
line: line,
sandboxName: sandboxName,
});
return;
}
@ -1034,7 +1048,7 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
throw new WebDriverError("Emulator callback still pending when finish() called");
}
if (cmd.id == that.sandbox.command_id) {
if (cmd.id == that.sandboxes[sandboxName].command_id) {
if (that.timer !== null) {
that.timer.cancel();
that.timer = null;
@ -1055,7 +1069,7 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
};
let chromeAsyncFinish = function() {
let res = that.sandbox.generate_results();
let res = that.sandboxes[sandboxName].generate_results();
chromeAsyncReturnFunc(res);
};
@ -1064,7 +1078,7 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
chromeAsyncReturnFunc(err);
};
if (!this.sandbox || newSandbox) {
if (newSandbox || !(sandboxName in this.sandboxes)) {
let marionette = new Marionette(
this,
win,
@ -1073,27 +1087,29 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
scriptTimeout,
this.heartbeatCallback,
this.testName);
this.sandbox = this.createExecuteSandbox(win, marionette, specialPowers);
if (!this.sandbox) {
return;
}
this.createExecuteSandbox(win, marionette,
specialPowers, sandboxName);
}
this.sandbox.command_id = cmd.id;
this.sandbox.runEmulatorCmd = (cmd, cb) => {
if (!this.sandboxes[sandboxName]) {
return;
}
this.sandboxes[sandboxName].command_id = cmd.id;
this.sandboxes[sandboxName].runEmulatorCmd = (cmd, cb) => {
let ecb = new EmulatorCallback();
ecb.onresult = cb;
ecb.onerror = chromeAsyncError;
this.emulator.pushCallback(ecb);
this.emulator.send({emulator_cmd: cmd, id: ecb.id});
};
this.sandbox.runEmulatorShell = (args, cb) => {
this.sandboxes[sandboxName].runEmulatorShell = (args, cb) => {
let ecb = new EmulatorCallback();
ecb.onresult = cb;
ecb.onerror = chromeAsyncError;
this.emulator.pushCallback(ecb);
this.emulator.send({emulator_shell: args, id: ecb.id});
};
this.applyArgumentsToSandbox(win, this.sandbox, args);
this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
// NB: win.onerror is not hooked by default due to the inability to
// differentiate content exceptions from chrome exceptions. See bug
@ -1115,8 +1131,8 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
}, that.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
}
this.sandbox.returnFunc = chromeAsyncReturnFunc;
this.sandbox.finish = chromeAsyncFinish;
this.sandboxes[sandboxName].returnFunc = chromeAsyncReturnFunc;
this.sandboxes[sandboxName].finish = chromeAsyncFinish;
if (!directInject) {
script = "__marionetteParams.push(returnFunc);" +
@ -1127,7 +1143,7 @@ GeckoDriver.prototype.executeWithCallback = function(cmd, resp, directInject) {
this.executeScriptInSandbox(
resp,
this.sandbox,
this.sandboxes[sandboxName],
script,
directInject,
true /* async */,
@ -1521,10 +1537,6 @@ GeckoDriver.prototype.switchToWindow = function(cmd, resp) {
}
if (found) {
// As in content, switching to a new window invalidates a sandbox
// for reuse.
this.sandbox = null;
// Initialise Marionette if browser has not been seen before,
// otherwise switch to known browser and activate the tab if it's a
// content browser.
@ -2452,6 +2464,7 @@ GeckoDriver.prototype.sessionTearDown = function(cmd, resp) {
}
this.observing = null;
}
this.sandboxes = {};
};
/**

View File

@ -791,10 +791,12 @@ class Marionette(object):
with self.using_context('content'):
perm = self.execute_script("""
let allow = arguments[0];
if (allow)
if (allow) {
allow = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
else
}
else {
allow = Components.interfaces.nsIPermissionManager.DENY_ACTION;
}
let perm_type = arguments[1];
Components.utils.import("resource://gre/modules/Services.jsm");
@ -826,7 +828,7 @@ class Marionette(object):
Components.utils.import("resource://gre/modules/Services.jsm");
let perm = arguments[0];
let secMan = Services.scriptSecurityManager;
let principal = secMan.getAppCodebasePrincipal(Services.io.newURI(perm.url, null, null),
let principal = secMan.getAppCodebasePrincipal(Services.io.newURI(perm.url, null, null),
perm.appId, perm.isInBrowserElement);
let testPerm = Services.perms.testPermissionFromPrincipal(principal, perm.type, perm.action);
if (testPerm == perm.action) {
@ -1349,7 +1351,7 @@ class Marionette(object):
def execute_js_script(self, script, script_args=None, async=True,
new_sandbox=True, special_powers=False,
script_timeout=None, inactivity_timeout=None,
filename=None):
filename=None, sandbox='default'):
if script_args is None:
script_args = []
args = self.wrapArguments(script_args)
@ -1367,7 +1369,7 @@ class Marionette(object):
return self.unwrapValue(response)
def execute_script(self, script, script_args=None, new_sandbox=True,
special_powers=False, script_timeout=None):
special_powers=False, sandbox='default', script_timeout=None):
'''
Executes a synchronous JavaScript script, and returns the result (or None if the script does return a value).
@ -1382,6 +1384,10 @@ class Marionette(object):
be used, since you already have access to chrome-level commands if you
set context to chrome and do an execute_script. This method was added
only to help us run existing Mochitests.
:param sandbox: A tag referring to the sandbox you wish to use; if
you specify a new tag, a new sandbox will be created. If you use the
special tag 'system', the sandbox will be created using the system
principal which has elevated privileges.
:param new_sandbox: If False, preserve global variables from the last
execute_*script call. This is True by default, in which case no
globals are preserved.
@ -1439,13 +1445,16 @@ class Marionette(object):
script=script,
args=args,
newSandbox=new_sandbox,
sandbox=sandbox,
specialPowers=special_powers,
scriptTimeout=script_timeout,
line=int(frame[1]),
filename=os.path.basename(frame[0]))
return self.unwrapValue(response)
def execute_async_script(self, script, script_args=None, new_sandbox=True, special_powers=False, script_timeout=None, debug_script=False):
def execute_async_script(self, script, script_args=None, new_sandbox=True,
sandbox='default', script_timeout=None,
special_powers=False, debug_script=False):
'''
Executes an asynchronous JavaScript script, and returns the result (or None if the script does return a value).
@ -1460,6 +1469,10 @@ class Marionette(object):
be used, since you already have access to chrome-level commands if you
set context to chrome and do an execute_script. This method was added
only to help us run existing Mochitests.
:param sandbox: A tag referring to the sandbox you wish to use; if
you specify a new tag, a new sandbox will be created. If you use the
special tag 'system', the sandbox will be created using the system
principal which has elevated privileges.
:param new_sandbox: If False, preserve global variables from the last
execute_*script call. This is True by default, in which case no
globals are preserved.
@ -1489,6 +1502,7 @@ class Marionette(object):
script=script,
args=args,
newSandbox=new_sandbox,
sandbox=sandbox,
specialPowers=special_powers,
scriptTimeout=script_timeout,
line=int(frame[1]),

View File

@ -46,9 +46,10 @@ let accessibility = new Accessibility();
let actions = new ActionChain(utils, checkForInterrupted);
let importedScripts = null;
// The sandbox we execute test scripts in. Gets lazily created in
// createExecuteContentSandbox().
let sandbox;
// A dict of sandboxes used this session
let sandboxes = {};
// The name of the current sandbox
let sandboxName = 'default';
// the unload handler
let onunload;
@ -85,7 +86,6 @@ let modalHandler = function() {
previousFrame = curFrame;
}
curFrame = content;
sandbox = null;
};
/**
@ -403,7 +403,7 @@ function sendError(err, cmdId) {
* Clear test values after completion of test
*/
function resetValues() {
sandbox = null;
sandboxes = {};
curFrame = content;
actions.mouseEventsOnly = false;
}
@ -437,7 +437,6 @@ function checkForInterrupted() {
//if previousFrame is set, then we're in a single process environment
curFrame = actions.frame = previousFrame;
previousFrame = null;
sandbox = null;
}
else {
//else we're in OOP environment, so we'll switch to the original OOP frame
@ -466,7 +465,12 @@ function createExecuteContentSandbox(win, timeout) {
mn.runEmulatorCmd = (cmd, cb) => this.runEmulatorCmd(cmd, cb);
mn.runEmulatorShell = (args, cb) => this.runEmulatorShell(args, cb);
let sandbox = new Cu.Sandbox(win, {sandboxPrototype: win});
let principal = win;
if (sandboxName == 'system') {
principal = Cc["@mozilla.org/systemprincipal;1"].
createInstance(Ci.nsIPrincipal);
}
let sandbox = new Cu.Sandbox(principal, {sandboxPrototype: win});
sandbox.global = sandbox;
sandbox.window = win;
sandbox.document = sandbox.window.document;
@ -532,7 +536,7 @@ function createExecuteContentSandbox(win, timeout) {
sandbox.marionetteScriptFinished = val =>
sandbox.asyncComplete(val, sandbox.asyncTestCommandId);
return sandbox;
sandboxes[sandboxName] = sandbox;
}
/**
@ -557,18 +561,22 @@ function executeScript(msg, directInject) {
asyncTestCommandId = msg.json.command_id;
let script = msg.json.script;
sandboxName = msg.json.sandboxName;
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
if (msg.json.newSandbox ||
!(sandboxName in sandboxes) ||
(sandboxes[sandboxName].window != curFrame)) {
createExecuteContentSandbox(curFrame, msg.json.timeout);
if (!sandboxes[sandboxName]) {
sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
return;
}
} else {
sandbox.asyncTestCommandId = asyncTestCommandId;
sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
}
let sandbox = sandboxes[sandboxName];
try {
if (directInject) {
if (importedScripts.exists()) {
@ -680,23 +688,26 @@ function executeWithCallback(msg, useFinish) {
let script = msg.json.script;
asyncTestCommandId = msg.json.command_id;
sandboxName = msg.json.sandboxName;
onunload = function() {
sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
};
curFrame.addEventListener("unload", onunload, false);
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
if (msg.json.newSandbox ||
!(sandboxName in sandboxes) ||
(sandboxes[sandboxName].window != curFrame)) {
createExecuteContentSandbox(curFrame, msg.json.timeout);
if (!sandboxes[sandboxName]) {
sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
return;
}
}
else {
sandbox.asyncTestCommandId = asyncTestCommandId;
sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
}
let sandbox = sandboxes[sandboxName];
sandbox.tag = script;
asyncTestTimeoutId = curFrame.setTimeout(function() {
@ -1642,7 +1653,7 @@ function switchToFrame(msg) {
if(msg.json.focus == true) {
curFrame.focus();
}
sandbox = null;
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
@ -1694,7 +1705,7 @@ function switchToFrame(msg) {
if(msg.json.focus == true) {
curFrame.focus();
}
sandbox = null;
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
@ -1716,8 +1727,6 @@ function switchToFrame(msg) {
return true;
}
sandbox = null;
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
@ -1877,7 +1886,7 @@ function runEmulatorShell(args, callback) {
function emulatorCmdResult(msg) {
let message = msg.json;
if (!sandbox) {
if (!sandboxes[sandboxName]) {
return;
}
let cb = _emu_cbs[message.id];