зеркало из https://github.com/mozilla/gecko-dev.git
403 строки
11 KiB
JavaScript
403 строки
11 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
/* import-globals-from browser_content_sandbox_utils.js */
|
|
"use strict";
|
|
|
|
Services.scriptloader.loadSubScript(
|
|
"chrome://mochitests/content/browser/" +
|
|
"security/sandbox/test/browser_content_sandbox_utils.js",
|
|
this
|
|
);
|
|
|
|
const lazy = {};
|
|
|
|
/* getLibcConstants is only present on *nix */
|
|
ChromeUtils.defineLazyGetter(lazy, "LIBC", () =>
|
|
ChromeUtils.getLibcConstants()
|
|
);
|
|
|
|
/*
|
|
* 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) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
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) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
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 sysctl syscall.
|
|
function callSysctl(args) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
let { lib, name } = args;
|
|
let libc = ctypes.open(lib);
|
|
let sysctlbyname = libc.declare(
|
|
"sysctlbyname",
|
|
ctypes.default_abi,
|
|
ctypes.int,
|
|
ctypes.char.ptr,
|
|
ctypes.voidptr_t,
|
|
ctypes.size_t.ptr,
|
|
ctypes.voidptr_t,
|
|
ctypes.size_t.ptr
|
|
);
|
|
let rv = sysctlbyname(name, null, null, null, null);
|
|
libc.close();
|
|
return rv;
|
|
}
|
|
|
|
function callPrctl(args) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
let { lib, option } = args;
|
|
let libc = ctypes.open(lib);
|
|
let prctl = libc.declare(
|
|
"prctl",
|
|
ctypes.default_abi,
|
|
ctypes.int,
|
|
ctypes.int, // option
|
|
ctypes.unsigned_long, // arg2
|
|
ctypes.unsigned_long, // arg3
|
|
ctypes.unsigned_long, // arg4
|
|
ctypes.unsigned_long // arg5
|
|
);
|
|
let rv = prctl(option, 0, 0, 0, 0);
|
|
if (rv == -1) {
|
|
rv = ctypes.errno;
|
|
}
|
|
libc.close();
|
|
return rv;
|
|
}
|
|
|
|
// Calls the native open/close syscalls.
|
|
function callOpen(args) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
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;
|
|
}
|
|
|
|
// Verify faccessat2
|
|
function callFaccessat2(args) {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
let { lib, dirfd, path, mode, flag } = args;
|
|
let libc = ctypes.open(lib);
|
|
let faccessat = libc.declare(
|
|
"faccessat",
|
|
ctypes.default_abi,
|
|
ctypes.int,
|
|
ctypes.int, // dirfd
|
|
ctypes.char.ptr, // path
|
|
ctypes.int, // mode
|
|
ctypes.int // flag
|
|
);
|
|
let rv = faccessat(dirfd, path, mode, flag);
|
|
if (rv == -1) {
|
|
rv = ctypes.errno;
|
|
}
|
|
libc.close();
|
|
return rv;
|
|
}
|
|
|
|
// 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");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Reading a header might be weird, but the alternatives to read a stable
|
|
// version number we can easily check against are not much more fun
|
|
async function getKernelVersion() {
|
|
let header = await IOUtils.readUTF8("/usr/include/linux/version.h");
|
|
let hr = header.split("\n");
|
|
for (let line in hr) {
|
|
let hrs = hr[line].split(" ");
|
|
if (hrs[0] === "#define" && hrs[1] === "LINUX_VERSION_CODE") {
|
|
return Number(hrs[2]);
|
|
}
|
|
}
|
|
throw Error("No LINUX_VERSION_CODE");
|
|
}
|
|
|
|
// This is how it is done in /usr/include/linux/version.h
|
|
function computeKernelVersion(major, minor, dot) {
|
|
return (major << 16) + (minor << 8) + dot;
|
|
}
|
|
|
|
function getGlibcVersion() {
|
|
const { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
let libc = ctypes.open(getOSLib());
|
|
let gnu_get_libc_version = libc.declare(
|
|
"gnu_get_libc_version",
|
|
ctypes.default_abi,
|
|
ctypes.char.ptr
|
|
);
|
|
let rv = gnu_get_libc_version().readString();
|
|
libc.close();
|
|
let ar = rv.split(".");
|
|
// return a number made of MAJORMINOR
|
|
return Number(ar[0] + ar[1]);
|
|
}
|
|
|
|
// 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 = 1;
|
|
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(async 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.
|
|
// eslint-disable-next-line mozilla/use-default-preference-values
|
|
try {
|
|
level = Services.prefs.getIntPref("security.sandbox.content.level");
|
|
} catch (e) {
|
|
prefExists = false;
|
|
}
|
|
|
|
ok(prefExists, "pref security.sandbox.content.level exists");
|
|
if (!prefExists) {
|
|
return;
|
|
}
|
|
|
|
info(`security.sandbox.content.level=${level}`);
|
|
ok(level > 0, "content sandbox is enabled.");
|
|
|
|
let areSyscallsSandboxed = areContentSyscallsSandboxed(level);
|
|
|
|
// 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 = await SpecialPowers.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 = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
|
|
let fd = await SpecialPowers.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 fail on
|
|
// macOS and work on Linux. The open handler in the content process closes
|
|
// the file for us
|
|
let path = fileInTempDir().path;
|
|
let flags = lazy.LIBC.O_CREAT | lazy.LIBC.O_WRONLY;
|
|
let fd = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, path, flags }],
|
|
callOpen
|
|
);
|
|
if (isMac()) {
|
|
ok(
|
|
fd === -1,
|
|
"opening a file for writing in content temp is not permitted"
|
|
);
|
|
} else {
|
|
ok(fd >= 0, "opening a file for writing in content temp is permitted");
|
|
}
|
|
}
|
|
|
|
// use fork syscall
|
|
if (isLinux() || isMac()) {
|
|
let rv = await SpecialPowers.spawn(browser, [{ lib }], callFork);
|
|
ok(rv == -1, "calling fork is not permitted");
|
|
}
|
|
|
|
// On macOS before 10.10 the |sysctl-name| predicate didn't exist for
|
|
// filtering |sysctl| access. Check the Darwin version before running the
|
|
// tests (Darwin 14.0.0 is macOS 10.10). This branch can be removed when we
|
|
// remove support for macOS 10.9.
|
|
if (isMac() && Services.sysinfo.getProperty("version") >= "14.0.0") {
|
|
let rv = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, name: "kern.boottime" }],
|
|
callSysctl
|
|
);
|
|
ok(rv == -1, "calling sysctl('kern.boottime') is not permitted");
|
|
|
|
rv = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, name: "net.inet.ip.ttl" }],
|
|
callSysctl
|
|
);
|
|
ok(rv == -1, "calling sysctl('net.inet.ip.ttl') is not permitted");
|
|
|
|
rv = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, name: "hw.ncpu" }],
|
|
callSysctl
|
|
);
|
|
ok(rv == 0, "calling sysctl('hw.ncpu') is permitted");
|
|
}
|
|
|
|
if (isLinux()) {
|
|
// These constants are not portable.
|
|
|
|
// verify we block PR_CAPBSET_READ with EINVAL
|
|
let option = lazy.LIBC.PR_CAPBSET_READ;
|
|
let rv = await SpecialPowers.spawn(browser, [{ lib, option }], callPrctl);
|
|
ok(rv === lazy.LIBC.EINVAL, "prctl(PR_CAPBSET_READ) is blocked");
|
|
|
|
const kernelVersion = await getKernelVersion();
|
|
const glibcVersion = getGlibcVersion();
|
|
// faccessat2 is only used with kernel 5.8+ by glibc 2.33+
|
|
if (glibcVersion >= 233 && kernelVersion >= computeKernelVersion(5, 8, 0)) {
|
|
info("Linux v5.8+, glibc 2.33+, checking faccessat2");
|
|
const dirfd = 0;
|
|
const path = "/";
|
|
const mode = 0;
|
|
// the value 0x01 is just one we know should get rejected
|
|
let rv = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, dirfd, path, mode, flag: 0x01 }],
|
|
callFaccessat2
|
|
);
|
|
ok(
|
|
rv === lazy.LIBC.ENOSYS,
|
|
"faccessat2 (flag=0x01) was blocked with ENOSYS"
|
|
);
|
|
|
|
rv = await SpecialPowers.spawn(
|
|
browser,
|
|
[{ lib, dirfd, path, mode, flag: lazy.LIBC.AT_EACCESS }],
|
|
callFaccessat2
|
|
);
|
|
ok(
|
|
rv === lazy.LIBC.EACCES,
|
|
"faccessat2 (flag=0x200) was allowed, errno=EACCES"
|
|
);
|
|
} else {
|
|
info(
|
|
"Unsupported kernel (" +
|
|
kernelVersion +
|
|
" )/glibc (" +
|
|
glibcVersion +
|
|
"), skipping faccessat2"
|
|
);
|
|
}
|
|
}
|
|
});
|