/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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 https://mozilla.org/MPL/2.0/. */
#include "SandboxTestingChild.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/ipc/UtilityProcessSandboxing.h"
#include "nsXULAppAPI.h"
#ifdef XP_UNIX
# include <fcntl.h>
# include <netdb.h>
# ifdef XP_LINUX
# include <arpa/inet.h>
# include <linux/mempolicy.h>
# include <sched.h>
# include <sys/ioctl.h>
# include <sys/mman.h>
# include <sys/prctl.h>
# include <sys/resource.h>
# include <sys/socket.h>
# include <sys/statfs.h>
# include <sys/syscall.h>
# include <sys/sysmacros.h>
# include <sys/time.h>
# include <sys/un.h>
# include <sys/utsname.h>
# include <termios.h>
# include
"mozilla/ProcInfo_linux.h"
# include
"mozilla/UniquePtrExtensions.h"
# ifdef MOZ_X11
# include
"X11/Xlib.h"
# include
"X11UndefineNone.h"
# endif
// MOZ_X11
# endif
// XP_LINUX
# include <sys/socket.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <time.h>
# include <unistd.h>
#endif
#ifdef XP_MACOSX
# if defined(__SSE2__) ||
defined(_M_X64) || \
(
defined(_M_IX86_FP) && _M_IX86_FP >= 2)
# include
"emmintrin.h"
# endif
# include <spawn.h>
# include <CoreFoundation/CoreFoundation.h>
# include <CoreGraphics/CoreGraphics.h>
# include <AudioToolbox/AudioToolbox.h>
namespace ApplicationServices {
# include <ApplicationServices/ApplicationServices.h>
}
#endif
#ifdef XP_WIN
# include <stdio.h>
# include <winternl.h>
# include
"mozilla/DynamicallyLinkedFunctionPtr.h"
# include
"nsAppDirectoryServiceDefs.h"
# include
"mozilla/WindowsProcessMitigations.h"
#endif
#ifdef XP_LINUX
// Defined in <linux/watch_queue.h> which was added in 5.8
# ifndef O_NOTIFICATION_PIPE
# define O_NOTIFICATION_PIPE O_EXCL
# endif
// Added in 5.7.
# ifndef MREMAP_DONTUNMAP
# define MREMAP_DONTUNMAP 4
# endif
#endif
constexpr
bool kIsDebug =
#ifdef DEBUG
true;
#else
false;
#endif
namespace mozilla {
#ifdef XP_LINUX
static void RunTestsSched(SandboxTestingChild* child) {
struct sched_param param_pid_0 = {};
child->ErrnoTest(
"sched_getparam(0)"_ns,
true,
[&] {
return sched_getparam(0, ¶m_pid_0); });
struct sched_param param_pid_tid = {};
child->ErrnoTest(
"sched_getparam(tid)"_ns,
true, [&] {
return sched_getparam((pid_t)syscall(__NR_gettid), ¶m_pid_tid);
});
struct sched_param param_pid_Ntid = {};
child->ErrnoValueTest(
"sched_getparam(Ntid)"_ns, EPERM, [&] {
return sched_getparam((pid_t)(syscall(__NR_gettid) - 1), ¶m_pid_Ntid);
});
}
#endif
// Tests that apply to every process type (more or less)
static void RunGenericTests(SandboxTestingChild* child,
bool aIsGMP =
false) {
#ifdef XP_LINUX
// Check ABI issues with 32-bit arguments on 64-bit platforms.
if (
sizeof(
void*) == 8) {
static constexpr uint64_t kHighBits = 0xDEADBEEF00000000;
struct timespec ts0, ts1;
child->ErrnoTest(
"high_bits_gettime"_ns,
true, [&] {
return syscall(__NR_clock_gettime, kHighBits | CLOCK_MONOTONIC, &ts0);
});
// Try to make sure we got the correct clock by reading it again and
// comparing to see if the times are vaguely similar.
int rv = clock_gettime(CLOCK_MONOTONIC, &ts1);
MOZ_RELEASE_ASSERT(rv == 0);
MOZ_RELEASE_ASSERT(ts0.tv_sec <= ts1.tv_sec + 1);
MOZ_RELEASE_ASSERT(ts1.tv_sec <= ts0.tv_sec + 60);
// Check some non-zeroth arguments. (fcntl is convenient for
// this, but GMP has a stricter policy, so skip it there.)
if (!aIsGMP) {
int flags;
child->ErrnoTest(
"high_bits_fcntl_getfl"_ns,
true, [&] {
flags = syscall(__NR_fcntl, 0, kHighBits | F_GETFL);
return flags;
});
MOZ_RELEASE_ASSERT(flags == fcntl(0, F_GETFL));
int fds[2];
rv = pipe(fds);
MOZ_RELEASE_ASSERT(rv >= 0);
child->ErrnoTest(
"high_bits_fcntl_setfl"_ns,
true, [&] {
return syscall(__NR_fcntl, fds[0], kHighBits | F_SETFL,
kHighBits | O_NONBLOCK);
});
flags = fcntl(fds[0], F_GETFL);
MOZ_RELEASE_ASSERT(flags >= 0);
MOZ_RELEASE_ASSERT(flags & O_NONBLOCK);
}
}
#endif // XP_LINUX
}
#ifdef XP_WIN
/**
* Uses NtCreateFile directly to test file system brokering.
*
*/
static void FileTest(
const nsCString& aName,
const char* aSpecialDirName,
const nsString& aRelativeFilePath, ACCESS_MASK aAccess,
bool aExpectSuccess, SandboxTestingChild* aChild) {
static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtCreateFile)>
pNtCreateFile(L
"ntdll.dll",
"NtCreateFile");
static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtClose)> pNtClose(
L
"ntdll.dll",
"NtClose");
// Start the filename with the NT namespace
nsString testFilename(u
"\\??\\"_ns);
nsString dirPath;
aChild->SendGetSpecialDirectory(nsDependentCString(aSpecialDirName),
&dirPath);
testFilename.Append(dirPath);
testFilename.AppendLiteral(
"\\");
testFilename.Append(aRelativeFilePath);
UNICODE_STRING uniFileName;
::RtlInitUnicodeString(&uniFileName, testFilename.get());
OBJECT_ATTRIBUTES objectAttributes;
InitializeObjectAttributes(&objectAttributes, &uniFileName,
OBJ_CASE_INSENSITIVE, nullptr, nullptr);
HANDLE fileHandle = INVALID_HANDLE_VALUE;
IO_STATUS_BLOCK ioStatusBlock = {};
ULONG createOptions = StringEndsWith(testFilename, u
"\\"_ns) ||
StringEndsWith(testFilename, u
"/"_ns)
? FILE_DIRECTORY_FILE
: FILE_NON_DIRECTORY_FILE;
NTSTATUS status = pNtCreateFile(
&fileHandle, aAccess, &objectAttributes, &ioStatusBlock, nullptr, 0, 0,
FILE_OPEN_IF, createOptions | FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
if (fileHandle != INVALID_HANDLE_VALUE) {
pNtClose(fileHandle);
}
nsCString accessString;
if ((aAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) {
accessString.AppendLiteral(
"r");
}
if ((aAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) {
accessString.AppendLiteral(
"w");
}
if ((aAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) {
accessString.AppendLiteral(
"e");
}
nsCString msgRelPath = NS_ConvertUTF16toUTF8(aRelativeFilePath);
for (size_t i = 0, j = 0; i < aRelativeFilePath.Length(); ++i, ++j) {
if (aRelativeFilePath[i] == u
'\\') {
msgRelPath.Insert(
'\\', j++);
}
}
nsCString message;
message.AppendPrintf(
"Special dir: %s, file: %s, access: %s , returned status: %lx",
aSpecialDirName, msgRelPath.get(), accessString.get(), status);
aChild->SendReportTestResults(aName, aExpectSuccess == NT_SUCCESS(status),
message);
}
#endif
#ifdef XP_MACOSX
/*
* Test if this process can launch another process with posix_spawnp,
* exec, and LSOpenCFURLRef. All launches are expected to fail. In processes
* where the sandbox permits reading of file metadata (content processes at
* this time), we expect the posix_spawnp error to be EPERM. In processes
* without that permission, we expect ENOENT. Changing the sandbox policy
* may break this assumption, but the important aspect to test for is that the
* launch is not permitted.
*/
void RunMacTestLaunchProcess(SandboxTestingChild* child,
int aPosixSpawnExpectedError = ENOENT) {
// Test that posix_spawnp fails
char* argv[2];
argv[0] =
const_cast<
char*>(
"bash");
argv[1] = NULL;
int rv = posix_spawnp(NULL,
"/bin/bash", NULL, NULL, argv, NULL);
nsPrintfCString posixSpawnMessage(
"posix_spawnp returned %d, expected %d", rv,
aPosixSpawnExpectedError);
child->SendReportTestResults(
"posix_spawnp test"_ns,
rv == aPosixSpawnExpectedError,
posixSpawnMessage);
// Test that exec fails
child->ErrnoTest(
"execv /bin/bash test"_ns,
false, [&] {
char* argvp = NULL;
return execv(
"/bin/bash", &argvp);
});
// Test that launching an application using LSOpenCFURLRef fails
char* uri =
const_cast<
char*>(
"/System/Applications/Utilities/Console.app");
CFStringRef filePath = ::CFStringCreateWithCString(kCFAllocatorDefault, uri,
kCFStringEncodingUTF8);
CFURLRef urlRef = ::CFURLCreateWithFileSystemPath(
kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle,
false);
if (!urlRef) {
child->SendReportTestResults(
"LSOpenCFURLRef"_ns,
false,
"CFURLCreateWithFileSystemPath failed"_ns);
return;
}
OSStatus status = ApplicationServices::LSOpenCFURLRef(urlRef, NULL);
::CFRelease(urlRef);
nsPrintfCString lsMessage(
"LSOpenCFURLRef returned %d, "
"expected kLSServerCommunicationErr (%d)",
status, ApplicationServices::kLSServerCommunicationErr);
child->SendReportTestResults(
"LSOpenCFURLRef"_ns,
status == ApplicationServices::kLSServerCommunicationErr, lsMessage);
}
/*
* Test if this process can connect to the macOS window server.
* When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
* When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
*/
void RunMacTestWindowServer(SandboxTestingChild* child,
bool aShouldHaveAccess =
false) {
// CGSessionCopyCurrentDictionary() returns NULL when a
// connection to the window server is not available.
CFDictionaryRef windowServerDict = CGSessionCopyCurrentDictionary();
bool gotWindowServerDetails = (windowServerDict != nullptr);
bool testPassed = (gotWindowServerDetails == aShouldHaveAccess);
child->SendReportTestResults(
"CGSessionCopyCurrentDictionary"_ns, testPassed,
gotWindowServerDetails
?
"dictionary returned, access is permitted"_ns
:
"no dictionary returned, access appears blocked"_ns);
if (windowServerDict != nullptr) {
CFRelease(windowServerDict);
}
}
/*
* Test if this process can get access to audio components on macOS.
* When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
* When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
*/
void RunMacTestAudioAPI(SandboxTestingChild* child,
bool aShouldHaveAccess =
false) {
AudioStreamBasicDescription inputFormat;
inputFormat.mFormatID = kAudioFormatMPEG4AAC;
inputFormat.mSampleRate = 48000.0;
inputFormat.mChannelsPerFrame = 2;
inputFormat.mBitsPerChannel = 0;
inputFormat.mFormatFlags = 0;
inputFormat.mFramesPerPacket = 1024;
inputFormat.mBytesPerPacket = 0;
UInt32 inputFormatSize =
sizeof(inputFormat);
OSStatus status = AudioFormatGetProperty(
kAudioFormatProperty_FormatInfo, 0, NULL, &inputFormatSize, &inputFormat);
bool gotAudioFormat = (status == 0);
bool testPassed = (gotAudioFormat == aShouldHaveAccess);
child->SendReportTestResults(
"AudioFormatGetProperty"_ns, testPassed,
gotAudioFormat ?
"got audio format, access is permitted"_ns
:
"no audio format, access appears blocked"_ns);
}
#endif /* XP_MACOSX */
#ifdef XP_WIN
void RunWinTestWin32k(SandboxTestingChild* child,
bool aShouldHaveAccess =
true) {
bool isLockedDown = (IsWin32kLockedDown() ==
true);
bool testPassed = (isLockedDown == aShouldHaveAccess);
child->SendReportTestResults(
"Win32kLockdown"_ns, testPassed,
isLockedDown ?
"got lockdown, access is blocked"_ns
:
"no lockdown, access appears permitted"_ns);
}
#endif // XP_WIN
void RunTestsContent(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#ifdef XP_UNIX
struct stat st;
static const char kAllowedPath[] =
"/usr/lib";
child->ErrnoTest(
"fstatat_as_stat"_ns,
true,
[&] {
return fstatat(AT_FDCWD, kAllowedPath, &st, 0); });
child->ErrnoTest(
"fstatat_as_lstat"_ns,
true, [&] {
return fstatat(AT_FDCWD, kAllowedPath, &st, AT_SYMLINK_NOFOLLOW);
});
# ifdef XP_LINUX
child->ErrnoTest(
"fstatat_as_fstat"_ns,
true,
[&] {
return fstatat(0,
"", &st, AT_EMPTY_PATH); });
const struct timespec usec = {0, 1000};
child->ErrnoTest(
"nanosleep"_ns,
true,
[&] {
return nanosleep(&usec, nullptr); });
struct timespec res = {0, 0};
child->ErrnoTest(
"clock_getres"_ns,
true,
[&] {
return clock_getres(CLOCK_REALTIME, &res); });
// same process is allowed
struct timespec tproc = {0, 0};
clockid_t same_process = MAKE_PROCESS_CPUCLOCK(getpid(), CPUCLOCK_SCHED);
child->ErrnoTest(
"clock_gettime_same_process"_ns,
true,
[&] {
return clock_gettime(same_process, &tproc); });
// different process is blocked by sandbox (SIGSYS, kernel would return
// EINVAL)
struct timespec tprocd = {0, 0};
clockid_t diff_process = MAKE_PROCESS_CPUCLOCK(1, CPUCLOCK_SCHED);
child->ErrnoValueTest(
"clock_gettime_diff_process"_ns, ENOSYS,
[&] {
return clock_gettime(diff_process, &tprocd); });
// thread is allowed
struct timespec tthread = {0, 0};
clockid_t thread =
MAKE_THREAD_CPUCLOCK((pid_t)syscall(__NR_gettid), CPUCLOCK_SCHED);
child->ErrnoTest(
"clock_gettime_thread"_ns,
true,
[&] {
return clock_gettime(thread, &tthread); });
// getcpu is allowed
// We're using syscall directly because:
// - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
// - getcpu isn't defined in the header files we're using yet.
int c;
child->ErrnoTest(
"getcpu"_ns,
true,
[&] {
return syscall(SYS_getcpu, &c, NULL, NULL); });
// An abstract socket that does not starts with '/', so we don't want it to
// work.
// Checking ENETUNREACH should be thrown by SandboxBrokerClient::Connect()
// when it detects it does not starts with a '/'
child->ErrnoValueTest(
"connect_abstract_blocked"_ns, ENETUNREACH, [&] {
int sockfd;
struct sockaddr_un addr;
char str[] =
"\0xyz";
// Abstract socket requires first byte to be NULL
size_t str_size = 4;
memset(&addr, 0,
sizeof(
struct sockaddr_un));
addr.sun_family = AF_UNIX;
memcpy(&addr.sun_path, str, str_size);
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
return -1;
}
int con_st = connect(sockfd, (
struct sockaddr*)&addr,
sizeof(sa_family_t) + str_size);
return con_st;
});
// An abstract socket that does starts with /, so we do want it to work.
// Checking ECONNREFUSED because this is what the broker should get
// when trying to establish the connect call for us if it's allowed;
// otherwise we get EACCES, meaning that it was passed to the broker
// (unlike the previous test) but rejected.
const int errorForX =
StaticPrefs::security_sandbox_content_headless_AtStartup() ? EACCES
: ECONNREFUSED;
child->ErrnoValueTest(
"connect_abstract_permit"_ns, errorForX, [&] {
int sockfd;
struct sockaddr_un addr;
// we re-use actual X path, because this is what is allowed within
// SandboxBrokerPolicyFactory::InitContentPolicy()
// We can't just use any random path allowed, but one with CONNECT allowed.
// (Note that the real X11 sockets have names like `X0` for
// display `:0`; there shouldn't be anything named just `X`.)
// Abstract socket requires first byte to be NULL
char str[] =
"\0/tmp/.X11-unix/X";
size_t str_size = 17;
memset(&addr, 0,
sizeof(
struct sockaddr_un));
addr.sun_family = AF_UNIX;
memcpy(&addr.sun_path, str, str_size);
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sockfd == -1) {
return -1;
}
int con_st = connect(sockfd, (
struct sockaddr*)&addr,
sizeof(sa_family_t) + str_size);
return con_st;
});
// Testing FIPS-relevant files, which need to be accessible
std::vector<std::pair<
const char*,
bool>> open_tests = {
{
"/dev/random",
true}};
// Not all systems have that file, so we only test access, if it exists
// in the first place
if (stat(
"/proc/sys/crypto/fips_enabled", &st) == 0) {
open_tests.push_back({
"/proc/sys/crypto/fips_enabled",
true});
}
for (
const std::pair<
const char*,
bool>& to_open : open_tests) {
child->ErrnoTest(
"open("_ns + nsCString(to_open.first) +
")"_ns,
to_open.second, [&] {
int fd = open(to_open.first, O_RDONLY);
if (to_open.second && fd > 0) {
close(fd);
}
return fd;
});
}
child->ErrnoTest(
"statfs"_ns,
true, [] {
struct statfs sf;
return statfs(
"/usr/share", &sf);
});
child->ErrnoTest(
"pipe2"_ns,
true, [] {
int fds[2];
int rv = pipe2(fds, O_CLOEXEC);
int savedErrno = errno;
if (rv == 0) {
close(fds[0]);
close(fds[1]);
}
errno = savedErrno;
return rv;
});
child->ErrnoValueTest(
"chroot"_ns, ENOSYS, [] {
return chroot(
"/"); });
child->ErrnoValueTest(
"pipe2_notif"_ns, ENOSYS, [] {
int fds[2];
return pipe2(fds, O_NOTIFICATION_PIPE);
});
# ifdef MOZ_X11
// Check that X11 access is blocked (bug 1129492).
// This will fail if security.sandbox.content.headless is turned off.
if (PR_GetEnv(
"DISPLAY")) {
Display* disp = XOpenDisplay(nullptr);
child->SendReportTestResults(
"x11_access"_ns, !disp,
disp ?
"XOpenDisplay succeeded"_ns :
"XOpenDisplay failed"_ns);
if (disp) {
XCloseDisplay(disp);
}
}
# endif
// MOZ_X11
child->ErrnoTest(
"realpath localtime"_ns,
true, [] {
char buf[PATH_MAX];
return realpath(
"/etc/localtime", buf) ? 0 : -1;
});
// Check that readlink truncates results longer than the buffer
// (rather than failing) and returns the total number of bytes
// actually written (not the size of the link or anything else).
{
char buf;
ssize_t rv = readlink(
"/etc/localtime", &buf, 1);
int err = errno;
if (rv == 1) {
child->SendReportTestResults(
"readlink truncate"_ns,
true,
"expected 1, got 1"_ns);
}
else if (rv < 0) {
nsPrintfCString msg(
"expected 1, got error: %s", strerror(err));
child->SendReportTestResults(
"readlink truncate"_ns,
false, msg);
}
else {
nsPrintfCString msg(
"expected 1, got %zd", rv);
child->SendReportTestResults(
"readlink truncate"_ns,
false, msg);
}
}
{
static constexpr size_t kMapSize = 65536;
void* mapping = mmap(nullptr, kMapSize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
MOZ_ASSERT(mapping != MAP_FAILED);
child->ErrnoTest(
"mremap-zero"_ns,
true, [&] {
void* rv = mremap(mapping, kMapSize, kMapSize, 0);
if (rv == MAP_FAILED) {
return -1;
}
MOZ_ASSERT(rv == mapping);
return 0;
});
child->ErrnoValueTest(
"mremap-forbidden"_ns, ENOSYS, [&] {
void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_DONTUNMAP);
// This is an invalid flag combination (DONTUNMAP requires
// MAYMOVE) so it will always fail with *something*.
MOZ_ASSERT(rv == MAP_FAILED);
return -1;
});
munmap(mapping, kMapSize);
}
# endif
// XP_LINUX
# ifdef XP_MACOSX
RunMacTestLaunchProcess(child, EPERM);
RunMacTestWindowServer(child);
RunMacTestAudioAPI(child,
true);
# endif
#elif XP_WIN
FileTest(
"read from chrome"_ns, NS_APP_USER_CHROME_DIR, u
"sandboxTest.txt"_ns,
FILE_GENERIC_READ,
true, child);
FileTest(
"read from profile via relative path"_ns, NS_APP_USER_CHROME_DIR,
u
"..\\sandboxTest.txt"_ns, FILE_GENERIC_READ,
false, child);
// The profile dir is the parent of the chrome dir.
FileTest(
"read from chrome using forward slash"_ns,
NS_APP_USER_PROFILE_50_DIR, u
"chrome/sandboxTest.txt"_ns,
FILE_GENERIC_READ,
false, child);
// Note: these only pass in DEBUG builds because we allow write access to the
// temp dir for certain test logs and that is where the profile is created.
FileTest(
"read from profile"_ns, NS_APP_USER_PROFILE_50_DIR,
u
"sandboxTest.txt"_ns, FILE_GENERIC_READ, kIsDebug, child);
FileTest(
"read/write from chrome"_ns, NS_APP_USER_CHROME_DIR,
u
"sandboxTest.txt"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE,
kIsDebug, child);
#else
child->ReportNoTests();
#endif
}
void RunTestsSocket(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#ifdef XP_UNIX
child->ErrnoTest(
"getaddrinfo"_ns,
true, [&] {
struct addrinfo* res;
int rv = getaddrinfo(
"localhost", nullptr, nullptr, &res);
if (res != nullptr) {
freeaddrinfo(res);
}
return rv;
});
# ifdef XP_LINUX
child->ErrnoTest(
"prctl_allowed"_ns,
true, [&] {
int rv = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
return rv;
});
child->ErrnoTest(
"prctl_blocked"_ns,
false, [&] {
int rv = prctl(PR_GET_SECCOMP, 0, 0, 0, 0);
return rv;
});
// Testing FIPS-relevant files, which need to be accessible
std::vector<std::pair<
const char*,
bool>> open_tests = {
{
"/dev/random",
true}};
// Not all systems have that file, so we only test access, if it exists
// in the first place
struct stat st;
if (stat(
"/proc/sys/crypto/fips_enabled", &st) == 0) {
open_tests.push_back({
"/proc/sys/crypto/fips_enabled",
true});
}
for (
const std::pair<
const char*,
bool>& to_open : open_tests) {
child->ErrnoTest(
"open("_ns + nsCString(to_open.first) +
")"_ns,
to_open.second, [&] {
int fd = open(to_open.first, O_RDONLY);
if (to_open.second && fd > 0) {
close(fd);
}
return fd;
});
}
// getcpu is allowed
// We're using syscall directly because:
// - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
// - getcpu isn't defined in the header files we're using yet.
int c;
child->ErrnoTest(
"getcpu"_ns,
true,
[&] {
return syscall(SYS_getcpu, &c, NULL, NULL); });
child->ErrnoTest(
"sendmsg"_ns,
true, [&] {
int fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
return fd;
}
struct sockaddr_in6 addr;
memset(&addr, 0,
sizeof(addr));
addr.sin6_family = AF_INET6;
// Address within 100::/64, i.e. IPv6 discard prefix.
inet_pton(AF_INET6,
"100::1", &addr.sin6_addr);
addr.sin6_port = htons(12345);
struct msghdr msg = {0};
struct iovec iov[1];
char buf[] =
"test";
iov[0].iov_base = buf;
iov[0].iov_len =
sizeof(buf);
msg.msg_iov = iov;
msg.msg_iovlen = 1;
msg.msg_name = &addr;
msg.msg_namelen =
sizeof(addr);
int rv = sendmsg(fd, &msg, 0);
close(fd);
MOZ_ASSERT(rv ==
sizeof(buf),
"Expected sendmsg to return the number of bytes sent");
return rv;
});
child->ErrnoTest(
"recvmmsg"_ns,
true, [&] {
int fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
return fd;
}
// Set the socket to non-blocking mode
int flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
close(fd);
return -1;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
close(fd);
return -1;
}
struct sockaddr_in6 addr;
memset(&addr, 0,
sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = htons(0);
if (bind(fd, (
struct sockaddr*)&addr,
sizeof(addr)) < 0) {
close(fd);
return -1;
}
struct mmsghdr msgs[1];
memset(msgs, 0,
sizeof(msgs));
struct iovec iov[1];
char buf[64];
iov[0].iov_base = buf;
iov[0].iov_len =
sizeof(buf);
msgs[0].msg_hdr.msg_iov = iov;
msgs[0].msg_hdr.msg_iovlen = 1;
int rv = recvmmsg(fd, msgs, 1, 0, nullptr);
close(fd);
MOZ_ASSERT(rv == -1 && errno == EAGAIN,
"recvmmsg should return -1 with EAGAIN given that no datagrams "
"are available");
return 0;
});
# endif
// XP_LINUX
#elif XP_MACOSX
RunMacTestLaunchProcess(child);
RunMacTestWindowServer(child);
RunMacTestAudioAPI(child);
#else // XP_UNIX
child->ReportNoTests();
#endif // XP_UNIX
}
void RunTestsRDD(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#ifdef XP_UNIX
# ifdef XP_LINUX
child->ErrnoValueTest(
"ioctl_tiocsti"_ns, ENOSYS, [&] {
int rv = ioctl(1, TIOCSTI,
"x");
return rv;
});
struct rusage res = {};
child->ErrnoTest(
"getrusage"_ns,
true, [&] {
int rv = getrusage(RUSAGE_SELF, &res);
return rv;
});
child->ErrnoValueTest(
"unlink"_ns, ENOENT, [&] {
int rv = unlink(
"");
return rv;
});
child->ErrnoValueTest(
"unlinkat"_ns, ENOENT, [&] {
int rv = unlinkat(AT_FDCWD,
"", 0);
return rv;
});
RunTestsSched(child);
child->ErrnoValueTest(
"socket_inet"_ns, EACCES,
[] {
return socket(AF_INET, SOCK_STREAM, 0); });
child->ErrnoValueTest(
"socket_unix"_ns, EACCES,
[] {
return socket(AF_UNIX, SOCK_STREAM, 0); });
child->ErrnoTest(
"uname"_ns,
true, [] {
struct utsname uts;
return uname(&uts);
});
child->ErrnoValueTest(
"ioctl_dma_buf"_ns, ENOTTY, [] {
// Apply the ioctl to the wrong kind of fd; it should fail with
// ENOTTY (rather than ENOSYS if it were blocked).
return ioctl(0, _IOW(
'b', 0, uint64_t), nullptr);
});
// getcpu is allowed
// We're using syscall directly because:
// - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
// - getcpu isn't defined in the header files we're using yet.
int c;
child->ErrnoTest(
"getcpu"_ns,
true,
[&] {
return syscall(SYS_getcpu, &c, NULL, NULL); });
// The nvidia proprietary drivers will, in some cases, try to
// mknod their device files; we reject this politely.
child->ErrnoValueTest(
"mknod"_ns, EPERM, [] {
return mknod(
"/dev/null", S_IFCHR | 0666, makedev(1, 3));
});
// Rust panics call getcwd to try to print relative paths in
// backtraces.
child->ErrnoValueTest(
"getcwd"_ns, ENOENT, [] {
char buf[4096];
return (getcwd(buf,
sizeof(buf)) == nullptr) ? -1 : 0;
});
// nvidia defines some ioctls with the type 0x46 ('F', otherwise
// used by fbdev) and numbers starting from 200 (0xc8).
child->ErrnoValueTest(
"ioctl_nvidia"_ns, ENOTTY,
[] {
return ioctl(0, 0x46c8, nullptr); });
child->ErrnoTest(
"statfs"_ns,
true, [] {
struct statfs sf;
return statfs(
"/usr/share", &sf);
});
child->ErrnoValueTest(
"fork"_ns, EPERM, [] {
pid_t pid = fork();
if (pid == 0) {
// Success: shouldn't happen, and parent will report a test
// failure.
_
exit(0);
}
return pid;
});
# elif XP_MACOSX
RunMacTestLaunchProcess(child);
RunMacTestWindowServer(child);
RunMacTestAudioAPI(child,
true);
# endif
#else // XP_UNIX
# ifdef XP_WIN
RunWinTestWin32k(child,
false);
# endif
// XP_WIN
child->ReportNoTests();
#endif
}
void RunTestsGMPlugin(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child,
/* aIsGMP = */ true);
#ifdef XP_UNIX
# ifdef XP_LINUX
struct utsname utsname_res = {};
child->ErrnoTest(
"uname_restricted"_ns,
true, [&] {
int rv = uname(&utsname_res);
nsCString expectedSysname(
"Linux"_ns);
nsCString sysname(utsname_res.sysname);
nsCString expectedVersion(
"3"_ns);
nsCString version(utsname_res.version);
if ((sysname != expectedSysname) || (version != expectedVersion)) {
return -1;
}
return rv;
});
child->ErrnoTest(
"getuid"_ns,
true, [&] {
return getuid(); });
child->ErrnoTest(
"getgid"_ns,
true, [&] {
return getgid(); });
child->ErrnoTest(
"geteuid"_ns,
true, [&] {
return geteuid(); });
child->ErrnoTest(
"getegid"_ns,
true, [&] {
return getegid(); });
RunTestsSched(child);
std::vector<std::pair<
const char*,
bool>> open_tests = {
{
"/etc/ld.so.cache",
true},
{
"/proc/cpuinfo",
true},
{
"/etc/hostname",
false}};
for (
const std::pair<
const char*,
bool>& to_open : open_tests) {
child->ErrnoTest(
"open("_ns + nsCString(to_open.first) +
")"_ns,
to_open.second, [&] {
int fd = open(to_open.first, O_RDONLY);
if (to_open.second && fd > 0) {
close(fd);
}
return fd;
});
}
child->ErrnoValueTest(
"readlink_exe"_ns, EINVAL, [] {
char pathBuf[PATH_MAX];
return readlink(
"/proc/self/exe", pathBuf,
sizeof(pathBuf));
});
child->ErrnoTest(
"memfd_sizing"_ns,
true, [] {
int fd = syscall(__NR_memfd_create,
"sandbox-test", 0);
if (fd < 0) {
if (errno == ENOSYS) {
// Don't fail the test if the kernel is old.
return 0;
}
return -1;
}
int rv = ftruncate(fd, 4096);
int savedErrno = errno;
close(fd);
errno = savedErrno;
return rv;
});
{
static constexpr size_t kMapSize = 65536;
void* mapping = mmap(nullptr, kMapSize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
MOZ_ASSERT(mapping != MAP_FAILED);
# ifndef MOZ_MEMORY
child->ErrnoTest(
"mremap-move"_ns,
true, [&] {
void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_MAYMOVE);
if (rv == MAP_FAILED) {
return -1;
}
// It *may* move the mapping, but when the size doesn't change
// it's not expected to:
MOZ_ASSERT(rv == mapping);
return 0;
});
# endif
child->ErrnoValueTest(
"mremap-forbidden"_ns, ENOSYS, [&] {
void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_DONTUNMAP);
// This is an invalid flag combination (DONTUNMAP requires
// MAYMOVE) so it will always fail with *something*.
MOZ_ASSERT(rv == MAP_FAILED);
return -1;
});
munmap(mapping, kMapSize);
}
# elif XP_MACOSX
// XP_LINUX
RunMacTestLaunchProcess(child);
/* The Mac GMP process requires access to the window server */
RunMacTestWindowServer(child,
true /* aShouldHaveAccess */);
RunMacTestAudioAPI(child);
# endif
// XP_MACOSX
#else // XP_UNIX
child->ReportNoTests();
#endif
}
void RunTestsGenericUtility(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#ifdef XP_UNIX
# ifdef XP_LINUX
child->ErrnoValueTest(
"ioctl_tiocsti"_ns, ENOSYS, [&] {
int rv = ioctl(1, TIOCSTI,
"x");
return rv;
});
struct rusage res;
child->ErrnoTest(
"getrusage"_ns,
true, [&] {
int rv = getrusage(RUSAGE_SELF, &res);
return rv;
});
# elif XP_MACOSX
// XP_LINUX
RunMacTestLaunchProcess(child);
RunMacTestWindowServer(child);
RunMacTestAudioAPI(child);
# endif
// XP_MACOSX
#elif XP_WIN
// XP_UNIX
child->ErrnoValueTest(
"write_only"_ns, EACCES, [&] {
FILE* rv = fopen(
"test_sandbox.txt",
"w");
if (rv != nullptr) {
fclose(rv);
return 0;
}
return -1;
});
RunWinTestWin32k(child);
#else // XP_UNIX
child->ReportNoTests();
#endif // XP_MACOSX
}
void RunTestsUtilityAudioDecoder(SandboxTestingChild* child,
ipc::SandboxingKind aSandbox) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#ifdef XP_UNIX
# ifdef XP_LINUX
// getrusage is allowed in Generic Utility and on AudioDecoder
struct rusage res;
child->ErrnoTest(
"getrusage"_ns,
true, [&] {
int rv = getrusage(RUSAGE_SELF, &res);
return rv;
});
// get_mempolicy is not allowed in Generic Utility but is on AudioDecoder
child->ErrnoTest(
"get_mempolicy"_ns,
true, [&] {
int numa_node;
int test_val = 0;
// <numaif.h> not installed by default, let's call directly the syscall
long rv = syscall(SYS_get_mempolicy, &numa_node, NULL, 0, (
void*)&test_val,
MPOL_F_NODE | MPOL_F_ADDR);
return rv;
});
// set_mempolicy is not allowed in Generic Utility but is on AudioDecoder
child->ErrnoValueTest(
"set_mempolicy"_ns, ENOSYS, [&] {
// <numaif.h> not installed by default, let's call directly the syscall
long rv = syscall(SYS_set_mempolicy, 0, NULL, 0);
return rv;
});
child->ErrnoValueTest(
"prctl_capbtest_read_blocked"_ns, EINVAL, [] {
int rv = prctl(PR_CAPBSET_READ, 0, 0, 0, 0);
return rv;
});
# elif XP_MACOSX
// XP_LINUX
RunMacTestLaunchProcess(child);
RunMacTestWindowServer(child);
RunMacTestAudioAPI(
child,
aSandbox == ipc::SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA);
# endif
// XP_MACOSX
#else // XP_UNIX
# ifdef XP_WIN
RunWinTestWin32k(child);
# endif
// XP_WIN
child->ReportNoTests();
#endif // XP_UNIX
}
void RunTestsGPU(SandboxTestingChild* child) {
MOZ_ASSERT(child,
"No SandboxTestingChild*?");
RunGenericTests(child);
#if defined(XP_WIN)
FileTest(
"R/W access to shader-cache dir"_ns, NS_APP_USER_PROFILE_50_DIR,
u
"shader-cache\\"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE,
true,
child);
FileTest(
"R/W access to shader-cache files"_ns, NS_APP_USER_PROFILE_50_DIR,
u
"shader-cache\\sandboxTest.txt"_ns,
FILE_GENERIC_READ | FILE_GENERIC_WRITE,
true, child);
#else // defined(XP_WIN)
child->ReportNoTests();
#endif // defined(XP_WIN)
}
}
// namespace mozilla