зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1309394 - automated tests to validate content process sandboxing works as intended; r=bobowen,gcp
Adds security/sandbox/test/browser_content_sandbox_fs.js for validating content sandbox file I/O restrictions. Adds security/sandbox/test/browser_content_sandbox_syscalls.js for validating OS-level calls are sandboxed as intended. Uses js-ctypes to invoke native library routines. Windows tests yet to be added here. Adds security/sandbox/test/browser_content_sandbox_utils.js with some shared utility functions. MozReview-Commit-ID: 5zfCLctfuN5 --HG-- extra : rebase_source : 4edd14220bcd18b15a3c522e44d7223547a79f43
This commit is contained in:
Родитель
d5608e0a47
Коммит
d144ed6ded
|
@ -4,6 +4,8 @@
|
|||
# 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/.
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
|
||||
|
||||
with Files('**'):
|
||||
BUG_COMPONENT = ('Core', 'Security: Process Sandboxing')
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
[DEFAULT]
|
||||
tags = contentsandbox
|
||||
support-files =
|
||||
browser_content_sandbox_utils.js
|
||||
|
||||
skip-if = !e10s
|
||||
[browser_content_sandbox_fs.js]
|
||||
skip-if = !e10s
|
||||
[browser_content_sandbox_syscalls.js]
|
|
@ -0,0 +1,169 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/" +
|
||||
"security/sandbox/test/browser_content_sandbox_utils.js", this);
|
||||
|
||||
/*
|
||||
* This test exercises file I/O from the content process using OS.File
|
||||
* methods to validate that calls that are meant to be blocked by content
|
||||
* sandboxing are blocked.
|
||||
*/
|
||||
|
||||
// Creates file at |path| and returns a promise that resolves with true
|
||||
// if the file was successfully created, otherwise false. Include imports
|
||||
// so this can be safely serialized and run remotely by ContentTask.spawn.
|
||||
function createFile(path) {
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
let encoder = new TextEncoder();
|
||||
let array = encoder.encode("WRITING FROM CONTENT PROCESS");
|
||||
return OS.File.writeAtomic(path, array).then(function(value) {
|
||||
return true;
|
||||
}, function(reason) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Deletes file at |path| and returns a promise that resolves with true
|
||||
// if the file was successfully deleted, otherwise false. Include imports
|
||||
// so this can be safely serialized and run remotely by ContentTask.spawn.
|
||||
function deleteFile(path) {
|
||||
Components.utils.import("resource://gre/modules/osfile.jsm");
|
||||
return OS.File.remove(path, {ignoreAbsent: false}).then(function(value) {
|
||||
return true;
|
||||
}).catch(function(err) {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Returns true if the current content sandbox level, passed in
|
||||
// the |level| argument, supports filesystem sandboxing.
|
||||
function isContentFileIOSandboxed(level) {
|
||||
let fileIOSandboxMinLevel = 0;
|
||||
|
||||
// Set fileIOSandboxMinLevel to the lowest level that has
|
||||
// content filesystem sandboxing enabled. For now, this
|
||||
// varies across Windows, Mac, Linux, other.
|
||||
switch (Services.appinfo.OS) {
|
||||
case "WINNT":
|
||||
fileIOSandboxMinLevel = 1;
|
||||
break;
|
||||
case "Darwin":
|
||||
fileIOSandboxMinLevel = 1;
|
||||
break;
|
||||
case "Linux":
|
||||
fileIOSandboxMinLevel = 2;
|
||||
break;
|
||||
default:
|
||||
Assert.ok(false, "Unknown OS");
|
||||
}
|
||||
|
||||
return (level >= fileIOSandboxMinLevel);
|
||||
}
|
||||
|
||||
//
|
||||
// Drive tests for a single content process.
|
||||
//
|
||||
// Tests attempting to write to a file in the home directory from the
|
||||
// content process--expected to fail.
|
||||
//
|
||||
// Tests attempting to write to a file in the content temp directory
|
||||
// from the content process--expected to succeed. On Mac and Windows,
|
||||
// use "ContentTmpD", but on Linux use "TmpD" until Linux uses the
|
||||
// content temp dir key.
|
||||
//
|
||||
add_task(function*() {
|
||||
// This test is only relevant in e10s
|
||||
if (!gMultiProcessBrowser) {
|
||||
ok(false, "e10s is enabled");
|
||||
info("e10s is not enabled, exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
let level = 0;
|
||||
let prefExists = true;
|
||||
|
||||
// Read the security.sandbox.content.level pref.
|
||||
// If the pref isn't set and we're running on Linux on !isNightly(),
|
||||
// exit without failing. The Linux content sandbox is only enabled
|
||||
// on Nightly at this time.
|
||||
try {
|
||||
level = prefs.getIntPref("security.sandbox.content.level");
|
||||
} catch (e) {
|
||||
prefExists = false;
|
||||
}
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(prefExists, "pref security.sandbox.content.level exists");
|
||||
if (!prefExists) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ok(prefExists, "pref security.sandbox.content.level exists");
|
||||
if (!prefExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(level > 0, "content sandbox enabled for !nightly.");
|
||||
return;
|
||||
}
|
||||
|
||||
info(`security.sandbox.content.level=${level}`);
|
||||
ok(level > 0, "content sandbox is enabled.");
|
||||
if (level == 0) {
|
||||
info("content sandbox is not enabled, exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
let isFileIOSandboxed = isContentFileIOSandboxed(level);
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(isFileIOSandboxed, "content file I/O sandbox enabled for !nightly.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Content sandbox enabled, but level doesn't include file I/O sandboxing.
|
||||
ok(isFileIOSandboxed, "content file I/O sandboxing is enabled.");
|
||||
if (!isFileIOSandboxed) {
|
||||
info("content sandbox level too low for file I/O tests, exiting\n");
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
|
||||
{
|
||||
// test if the content process can create in $HOME, this should fail
|
||||
let homeFile = fileInHomeDir();
|
||||
let path = homeFile.path;
|
||||
let fileCreated = yield ContentTask.spawn(browser, path, createFile);
|
||||
ok(fileCreated == false, "creating a file in home dir is not permitted");
|
||||
if (fileCreated == true) {
|
||||
// content process successfully created the file, now remove it
|
||||
homeFile.remove(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// test if the content process can create a temp file, should pass
|
||||
let path = fileInTempDir().path;
|
||||
let fileCreated = yield ContentTask.spawn(browser, path, createFile);
|
||||
if (!fileCreated && isWin()) {
|
||||
// TODO: fix 1329294 and enable this test for Windows.
|
||||
// Not using todo() because this only fails on automation.
|
||||
info("ignoring failure to write to content temp due to 1329294\n");
|
||||
return;
|
||||
}
|
||||
ok(fileCreated == true, "creating a file in content temp is permitted");
|
||||
// now delete the file
|
||||
let fileDeleted = yield ContentTask.spawn(browser, path, deleteFile);
|
||||
ok(fileDeleted == true, "deleting a file in content temp is permitted");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,223 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var prefs = Cc["@mozilla.org/preferences-service;1"]
|
||||
.getService(Ci.nsIPrefBranch);
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/" +
|
||||
"security/sandbox/test/browser_content_sandbox_utils.js", this);
|
||||
|
||||
/*
|
||||
* This test is for executing system calls in content processes to validate
|
||||
* that calls that are meant to be blocked by content sandboxing are blocked.
|
||||
* We use the term system calls loosely so that any OS API call such as
|
||||
* fopen could be included.
|
||||
*/
|
||||
|
||||
// Calls the native execv library function. Include imports so this can be
|
||||
// safely serialized and run remotely by ContentTask.spawn.
|
||||
function callExec(args) {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
let {lib, cmd} = args;
|
||||
let libc = ctypes.open(lib);
|
||||
let exec = libc.declare("execv", ctypes.default_abi,
|
||||
ctypes.int, ctypes.char.ptr);
|
||||
let rv = exec(cmd);
|
||||
libc.close();
|
||||
return (rv);
|
||||
}
|
||||
|
||||
// Calls the native fork syscall.
|
||||
function callFork(args) {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
let {lib} = args;
|
||||
let libc = ctypes.open(lib);
|
||||
let fork = libc.declare("fork", ctypes.default_abi, ctypes.int);
|
||||
let rv = fork();
|
||||
libc.close();
|
||||
return (rv);
|
||||
}
|
||||
|
||||
// Calls the native open/close syscalls.
|
||||
function callOpen(args) {
|
||||
Components.utils.import("resource://gre/modules/ctypes.jsm");
|
||||
let {lib, path, flags} = args;
|
||||
let libc = ctypes.open(lib);
|
||||
let open = libc.declare("open", ctypes.default_abi,
|
||||
ctypes.int, ctypes.char.ptr, ctypes.int);
|
||||
let close = libc.declare("close", ctypes.default_abi,
|
||||
ctypes.int, ctypes.int);
|
||||
let fd = open(path, flags);
|
||||
close(fd);
|
||||
libc.close();
|
||||
return (fd);
|
||||
}
|
||||
|
||||
// open syscall flags
|
||||
function openWriteCreateFlags() {
|
||||
Assert.ok(isMac() || isLinux());
|
||||
if (isMac()) {
|
||||
let O_WRONLY = 0x001;
|
||||
let O_CREAT = 0x200;
|
||||
return (O_WRONLY | O_CREAT);
|
||||
} else {
|
||||
// Linux
|
||||
let O_WRONLY = 0x01;
|
||||
let O_CREAT = 0x40;
|
||||
return (O_WRONLY | O_CREAT);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the name of the native library needed for native syscalls
|
||||
function getOSLib() {
|
||||
switch (Services.appinfo.OS) {
|
||||
case "WINNT":
|
||||
return "kernel32.dll";
|
||||
case "Darwin":
|
||||
return "libc.dylib";
|
||||
case "Linux":
|
||||
return "libc.so.6";
|
||||
default:
|
||||
Assert.ok(false, "Unknown OS");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a harmless command to execute with execv
|
||||
function getOSExecCmd() {
|
||||
Assert.ok(!isWin());
|
||||
return ("/bin/cat");
|
||||
}
|
||||
|
||||
// Returns true if the current content sandbox level, passed in
|
||||
// the |level| argument, supports syscall sandboxing.
|
||||
function areContentSyscallsSandboxed(level) {
|
||||
let syscallsSandboxMinLevel = 0;
|
||||
|
||||
// Set syscallsSandboxMinLevel to the lowest level that has
|
||||
// syscall sandboxing enabled. For now, this varies across
|
||||
// Windows, Mac, Linux, other.
|
||||
switch (Services.appinfo.OS) {
|
||||
case "WINNT":
|
||||
syscallsSandboxMinLevel = 1;
|
||||
break;
|
||||
case "Darwin":
|
||||
syscallsSandboxMinLevel = 1;
|
||||
break;
|
||||
case "Linux":
|
||||
syscallsSandboxMinLevel = 2;
|
||||
break;
|
||||
default:
|
||||
Assert.ok(false, "Unknown OS");
|
||||
}
|
||||
|
||||
return (level >= syscallsSandboxMinLevel);
|
||||
}
|
||||
|
||||
//
|
||||
// Drive tests for a single content process.
|
||||
//
|
||||
// Tests executing OS API calls in the content process. Limited to Mac
|
||||
// and Linux calls for now.
|
||||
//
|
||||
add_task(function*() {
|
||||
// This test is only relevant in e10s
|
||||
if (!gMultiProcessBrowser) {
|
||||
ok(false, "e10s is enabled");
|
||||
info("e10s is not enabled, exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
let level = 0;
|
||||
let prefExists = true;
|
||||
|
||||
// Read the security.sandbox.content.level pref.
|
||||
// If the pref isn't set and we're running on Linux on !isNightly(),
|
||||
// exit without failing. The Linux content sandbox is only enabled
|
||||
// on Nightly at this time.
|
||||
try {
|
||||
level = prefs.getIntPref("security.sandbox.content.level");
|
||||
} catch (e) {
|
||||
prefExists = false;
|
||||
}
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(prefExists, "pref security.sandbox.content.level exists");
|
||||
if (!prefExists) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ok(prefExists, "pref security.sandbox.content.level exists");
|
||||
if (!prefExists) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(level > 0, "content sandbox enabled for !nightly.");
|
||||
return;
|
||||
}
|
||||
|
||||
info(`security.sandbox.content.level=${level}`);
|
||||
ok(level > 0, "content sandbox is enabled.");
|
||||
if (level == 0) {
|
||||
info("content sandbox is not enabled, exiting");
|
||||
return;
|
||||
}
|
||||
|
||||
let areSyscallsSandboxed = areContentSyscallsSandboxed(level);
|
||||
|
||||
// Special case Linux on !isNightly
|
||||
if (isLinux() && !isNightly()) {
|
||||
todo(areSyscallsSandboxed, "content syscall sandbox enabled for !nightly.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Content sandbox enabled, but level doesn't include syscall sandboxing.
|
||||
ok(areSyscallsSandboxed, "content syscall sandboxing is enabled.");
|
||||
if (!areSyscallsSandboxed) {
|
||||
info("content sandbox level too low for syscall tests, exiting\n");
|
||||
return;
|
||||
}
|
||||
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
let lib = getOSLib();
|
||||
|
||||
// use execv syscall
|
||||
// (causes content process to be killed on Linux)
|
||||
if (isMac()) {
|
||||
// exec something harmless, this should fail
|
||||
let cmd = getOSExecCmd();
|
||||
let rv = yield ContentTask.spawn(browser, {lib, cmd}, callExec);
|
||||
ok(rv == -1, `exec(${cmd}) is not permitted`);
|
||||
}
|
||||
|
||||
// use open syscall
|
||||
if (isLinux() || isMac())
|
||||
{
|
||||
// open a file for writing in $HOME, this should fail
|
||||
let path = fileInHomeDir().path;
|
||||
let flags = openWriteCreateFlags();
|
||||
let fd = yield ContentTask.spawn(browser, {lib, path, flags}, callOpen);
|
||||
ok(fd < 0, "opening a file for writing in home is not permitted");
|
||||
}
|
||||
|
||||
// use open syscall
|
||||
if (isLinux() || isMac())
|
||||
{
|
||||
// open a file for writing in the content temp dir, this should work
|
||||
// and the open handler in the content process closes the file for us
|
||||
let path = fileInTempDir().path;
|
||||
let flags = openWriteCreateFlags();
|
||||
let fd = yield ContentTask.spawn(browser, {lib, path, flags}, callOpen);
|
||||
ok(fd >= 0, "opening a file for writing in content temp is permitted");
|
||||
}
|
||||
|
||||
// use fork syscall
|
||||
if (isLinux() || isMac())
|
||||
{
|
||||
let rv = yield ContentTask.spawn(browser, {lib}, callFork);
|
||||
ok(rv == -1, "calling fork is not permitted");
|
||||
}
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
|
||||
.getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/*
|
||||
* Utility functions for the browser content sandbox tests.
|
||||
*/
|
||||
|
||||
function isMac() { return Services.appinfo.OS == "Darwin" }
|
||||
function isWin() { return Services.appinfo.OS == "WINNT" }
|
||||
function isLinux() { return Services.appinfo.OS == "Linux" }
|
||||
|
||||
function isNightly() {
|
||||
let version = SpecialPowers.Cc["@mozilla.org/xre/app-info;1"].
|
||||
getService(SpecialPowers.Ci.nsIXULAppInfo).version;
|
||||
return (version.endsWith("a1"));
|
||||
}
|
||||
|
||||
function uuid() {
|
||||
return uuidGenerator.generateUUID().toString();
|
||||
}
|
||||
|
||||
// Returns a file object for a new file in the home dir ($HOME/<UUID>).
|
||||
function fileInHomeDir() {
|
||||
// get home directory, make sure it exists
|
||||
let homeDir = Services.dirsvc.get("Home", Ci.nsILocalFile);
|
||||
Assert.ok(homeDir.exists(), "Home dir exists");
|
||||
Assert.ok(homeDir.isDirectory(), "Home dir is a directory");
|
||||
|
||||
// build a file object for a new file named $HOME/<UUID>
|
||||
let homeFile = homeDir.clone();
|
||||
homeFile.appendRelativePath(uuid());
|
||||
Assert.ok(!homeFile.exists(), homeFile.path + " does not exist");
|
||||
return (homeFile);
|
||||
}
|
||||
|
||||
// Returns a file object for a new file in the content temp dir (.../<UUID>).
|
||||
function fileInTempDir() {
|
||||
let contentTempKey = "ContentTmpD";
|
||||
if (Services.appinfo.OS == "Linux") {
|
||||
// Linux builds don't use the content-specific temp key
|
||||
contentTempKey = "TmpD";
|
||||
}
|
||||
|
||||
// get the content temp dir, make sure it exists
|
||||
let ctmp = Services.dirsvc.get(contentTempKey, Ci.nsILocalFile);
|
||||
Assert.ok(ctmp.exists(), "Content temp dir exists");
|
||||
Assert.ok(ctmp.isDirectory(), "Content temp dir is a directory");
|
||||
|
||||
// build a file object for a new file in content temp
|
||||
let tempFile = ctmp.clone();
|
||||
tempFile.appendRelativePath(uuid());
|
||||
Assert.ok(!tempFile.exists(), tempFile.path + " does not exist");
|
||||
return (tempFile);
|
||||
}
|
Загрузка…
Ссылка в новой задаче