diff options
Diffstat (limited to 'security/sandbox/common/test/SandboxTestingChildTests.h')
-rw-r--r-- | security/sandbox/common/test/SandboxTestingChildTests.h | 824 |
1 files changed, 824 insertions, 0 deletions
diff --git a/security/sandbox/common/test/SandboxTestingChildTests.h b/security/sandbox/common/test/SandboxTestingChildTests.h new file mode 100644 index 0000000000..f26bc68af0 --- /dev/null +++ b/security/sandbox/common/test/SandboxTestingChildTests.h @@ -0,0 +1,824 @@ +/* -*- 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" +#ifdef XP_MACOSX +# include "nsCocoaFeatures.h" +#endif +#include "nsXULAppAPI.h" + +#ifdef XP_UNIX +# include <fcntl.h> +# include <netdb.h> +# ifdef XP_LINUX +# include <linux/mempolicy.h> +# include <sched.h> +# include <sys/ioctl.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 + +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; + if (nsCocoaFeatures::OnCatalinaOrLater()) { + uri = const_cast<char*>("/System/Applications/Utilities/Console.app"); + } else { + uri = const_cast<char*>("/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); + }); + +# 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 +# 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); }); +# 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->ErrnoTest("socket_inet"_ns, false, + [] { return socket(AF_INET, SOCK_STREAM, 0); }); + + child->ErrnoTest("socket_unix"_ns, false, + [] { 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)); + }); + + // 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); + }); + +# 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; + }); + +# 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; + }); +# 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 +} + +} // namespace mozilla |