diff options
Diffstat (limited to 'security/sandbox/test')
-rw-r--r-- | security/sandbox/test/browser.ini | 11 | ||||
-rw-r--r-- | security/sandbox/test/browser_content_sandbox_fs.js | 169 | ||||
-rw-r--r-- | security/sandbox/test/browser_content_sandbox_syscalls.js | 223 | ||||
-rw-r--r-- | security/sandbox/test/browser_content_sandbox_utils.js | 57 |
4 files changed, 460 insertions, 0 deletions
diff --git a/security/sandbox/test/browser.ini b/security/sandbox/test/browser.ini new file mode 100644 index 000000000..7e3e96ea4 --- /dev/null +++ b/security/sandbox/test/browser.ini @@ -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] diff --git a/security/sandbox/test/browser_content_sandbox_fs.js b/security/sandbox/test/browser_content_sandbox_fs.js new file mode 100644 index 000000000..946f86bb9 --- /dev/null +++ b/security/sandbox/test/browser_content_sandbox_fs.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"); + } +}); diff --git a/security/sandbox/test/browser_content_sandbox_syscalls.js b/security/sandbox/test/browser_content_sandbox_syscalls.js new file mode 100644 index 000000000..d56456966 --- /dev/null +++ b/security/sandbox/test/browser_content_sandbox_syscalls.js @@ -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"); + } +}); diff --git a/security/sandbox/test/browser_content_sandbox_utils.js b/security/sandbox/test/browser_content_sandbox_utils.js new file mode 100644 index 000000000..0e9d01352 --- /dev/null +++ b/security/sandbox/test/browser_content_sandbox_utils.js @@ -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); +} |