tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SandboxTestingChildTests.h (44200B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SandboxTestingChild.h"
      8 
      9 #include "mozilla/ipc/UtilityProcessSandboxing.h"
     10 #include "nsXULAppAPI.h"
     11 
     12 #ifdef XP_UNIX
     13 #  include <fcntl.h>
     14 #  include <netdb.h>
     15 #  ifdef XP_LINUX
     16 #    include <arpa/inet.h>
     17 #    include <linux/memfd.h>
     18 #    include <linux/mempolicy.h>
     19 #    include <linux/mman.h>
     20 #    include <sched.h>
     21 #    include <sys/ioctl.h>
     22 #    include <sys/mman.h>
     23 #    include <sys/prctl.h>
     24 #    include <sys/resource.h>
     25 #    include <sys/socket.h>
     26 #    include <sys/statfs.h>
     27 #    include <sys/syscall.h>
     28 #    include <sys/sysmacros.h>
     29 #    include <sys/time.h>
     30 #    include <sys/un.h>
     31 #    include <sys/utsname.h>
     32 #    include <termios.h>
     33 #    include "mozilla/ProcInfo_linux.h"
     34 #    ifdef MOZ_X11
     35 #      include "X11/Xlib.h"
     36 #      include "X11UndefineNone.h"
     37 #    endif  // MOZ_X11
     38 #  endif    // XP_LINUX
     39 #  include <sys/socket.h>
     40 #  include <sys/stat.h>
     41 #  include <sys/types.h>
     42 #  include <time.h>
     43 #  include <unistd.h>
     44 #endif
     45 
     46 #ifdef XP_MACOSX
     47 #  if defined(__SSE2__) || defined(_M_X64) || \
     48      (defined(_M_IX86_FP) && _M_IX86_FP >= 2)
     49 #    include "emmintrin.h"
     50 #  endif
     51 #  include <spawn.h>
     52 #  include <CoreFoundation/CoreFoundation.h>
     53 #  include <CoreGraphics/CoreGraphics.h>
     54 #  include <AudioToolbox/AudioToolbox.h>
     55 #  include <dirent.h>
     56 #  include <fcntl.h>
     57 #  include <limits.h>
     58 #  include <sys/stat.h>
     59 #  include <sys/sysctl.h>
     60 #  include <unistd.h>
     61 namespace ApplicationServices {
     62 #  include <ApplicationServices/ApplicationServices.h>
     63 }
     64 extern "C" int sandbox_check(pid_t pid, const char* operation, int type, ...);
     65 #endif
     66 
     67 #ifdef XP_WIN
     68 #  include <stdio.h>
     69 #  include <winternl.h>
     70 
     71 #  include "mozilla/DynamicallyLinkedFunctionPtr.h"
     72 #  include "nsAppDirectoryServiceDefs.h"
     73 #  include "mozilla/WindowsProcessMitigations.h"
     74 #endif
     75 
     76 #ifdef XP_LINUX
     77 // Defined in <linux/watch_queue.h> which was added in 5.8
     78 #  ifndef O_NOTIFICATION_PIPE
     79 #    define O_NOTIFICATION_PIPE O_EXCL
     80 #  endif
     81 // Added in 5.7.
     82 #  ifndef MREMAP_DONTUNMAP
     83 #    define MREMAP_DONTUNMAP 4
     84 #  endif
     85 // Added in 4.14.
     86 #  ifndef MFD_HUGETLB
     87 #    define MFD_HUGETLB 4U
     88 #    define MFD_HUGE_2MB (21U << 26)
     89 #  endif
     90 // (MAP_HUGE_* is from 3.8.  MAP_HUGETLB is 2.6.32.)
     91 //
     92 // This constant is ancient, but the kernel header for it conflicts
     93 // with glibc's fcntl.h:
     94 #  ifndef F_LINUX_SPECIFIC_BASE
     95 #    define F_LINUX_SPECIFIC_BASE 1024
     96 #  endif
     97 // Added in 6.10:
     98 #  ifndef F_DUPFD_QUERY
     99 #    define F_DUPFD_QUERY (F_LINUX_SPECIFIC_BASE + 3)
    100 #  endif
    101 #endif
    102 
    103 constexpr bool kIsDebug =
    104 #ifdef DEBUG
    105    true;
    106 #else
    107    false;
    108 #endif
    109 
    110 namespace mozilla {
    111 
    112 #ifdef XP_LINUX
    113 static void RunTestsSched(SandboxTestingChild* child) {
    114  struct sched_param param_pid_0 = {};
    115  child->ErrnoTest("sched_getparam(0)"_ns, true,
    116                   [&] { return sched_getparam(0, &param_pid_0); });
    117 
    118  struct sched_param param_pid_tid = {};
    119  child->ErrnoTest("sched_getparam(tid)"_ns, true, [&] {
    120    return sched_getparam((pid_t)syscall(__NR_gettid), &param_pid_tid);
    121  });
    122 
    123  struct sched_param param_pid_Ntid = {};
    124  child->ErrnoValueTest("sched_getparam(Ntid)"_ns, EPERM, [&] {
    125    return sched_getparam((pid_t)(syscall(__NR_gettid) - 1), &param_pid_Ntid);
    126  });
    127 }
    128 #endif
    129 
    130 // Tests that apply to every process type (more or less)
    131 static void RunGenericTests(SandboxTestingChild* child, bool aIsGMP = false) {
    132 #ifdef XP_LINUX
    133  // Check ABI issues with 32-bit arguments on 64-bit platforms.
    134  if (sizeof(void*) == 8) {
    135    static constexpr uint64_t kHighBits = 0xDEADBEEF00000000;
    136 
    137    struct timespec ts0, ts1;
    138    child->ErrnoTest("high_bits_gettime"_ns, true, [&] {
    139      return syscall(__NR_clock_gettime, kHighBits | CLOCK_MONOTONIC, &ts0);
    140    });
    141    // Try to make sure we got the correct clock by reading it again and
    142    // comparing to see if the times are vaguely similar.
    143    int rv = clock_gettime(CLOCK_MONOTONIC, &ts1);
    144    MOZ_RELEASE_ASSERT(rv == 0);
    145    MOZ_RELEASE_ASSERT(ts0.tv_sec <= ts1.tv_sec + 1);
    146    MOZ_RELEASE_ASSERT(ts1.tv_sec <= ts0.tv_sec + 60);
    147 
    148    // Check some non-zeroth arguments.  (fcntl is convenient for
    149    // this, but GMP has a stricter policy, so skip it there.)
    150    if (!aIsGMP) {
    151      int flags;
    152      child->ErrnoTest("high_bits_fcntl_getfl"_ns, true, [&] {
    153        flags = syscall(__NR_fcntl, 0, kHighBits | F_GETFL);
    154        return flags;
    155      });
    156      MOZ_RELEASE_ASSERT(flags == fcntl(0, F_GETFL));
    157 
    158      int fds[2];
    159      rv = pipe(fds);
    160      MOZ_RELEASE_ASSERT(rv >= 0);
    161      child->ErrnoTest("high_bits_fcntl_setfl"_ns, true, [&] {
    162        return syscall(__NR_fcntl, fds[0], kHighBits | F_SETFL,
    163                       kHighBits | O_NONBLOCK);
    164      });
    165      flags = fcntl(fds[0], F_GETFL);
    166      MOZ_RELEASE_ASSERT(flags >= 0);
    167      MOZ_RELEASE_ASSERT(flags & O_NONBLOCK);
    168    }
    169  }
    170 
    171  if (!aIsGMP) {
    172    constexpr auto name = "fcntl_dupfd_query"_ns;
    173    int rv = fcntl(0, F_DUPFD_QUERY, 0);
    174    // Expected:
    175    // * success with rv == 1 (new kernel)
    176    // * failure with EINVAL (old kernel)
    177    // Rejected:
    178    // * failure with ENOSYS or any other error
    179    // * success with rv == 0 (shouldn't be possible)
    180    MOZ_RELEASE_ASSERT(rv != 0);
    181    if (rv > 0) {
    182      child->PosixTest(name, true, 0);
    183    } else {  // (rv < 0), errno unchanged since fcntl
    184      child->PosixTest(name, false, errno, Some(EINVAL));
    185    }
    186  }
    187 #endif  // XP_LINUX
    188 }
    189 
    190 #ifdef XP_WIN
    191 /**
    192 * Uses NtCreateFile directly to test file system brokering.
    193 *
    194 */
    195 static void FileTest(const nsCString& aName, const char* aSpecialDirName,
    196                     const nsString& aRelativeFilePath, ACCESS_MASK aAccess,
    197                     bool aExpectSuccess, SandboxTestingChild* aChild) {
    198  static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtCreateFile)>
    199      pNtCreateFile(L"ntdll.dll", "NtCreateFile");
    200  static const StaticDynamicallyLinkedFunctionPtr<decltype(&NtClose)> pNtClose(
    201      L"ntdll.dll", "NtClose");
    202 
    203  // Start the filename with the NT namespace
    204  nsString testFilename(u"\\??\\"_ns);
    205  nsString dirPath;
    206  aChild->SendGetSpecialDirectory(nsDependentCString(aSpecialDirName),
    207                                  &dirPath);
    208  testFilename.Append(dirPath);
    209  testFilename.AppendLiteral("\\");
    210  testFilename.Append(aRelativeFilePath);
    211 
    212  UNICODE_STRING uniFileName;
    213  ::RtlInitUnicodeString(&uniFileName, testFilename.get());
    214 
    215  OBJECT_ATTRIBUTES objectAttributes;
    216  InitializeObjectAttributes(&objectAttributes, &uniFileName,
    217                             OBJ_CASE_INSENSITIVE, nullptr, nullptr);
    218 
    219  HANDLE fileHandle = INVALID_HANDLE_VALUE;
    220  IO_STATUS_BLOCK ioStatusBlock = {};
    221 
    222  ULONG createOptions = StringEndsWith(testFilename, u"\\"_ns) ||
    223                                StringEndsWith(testFilename, u"/"_ns)
    224                            ? FILE_DIRECTORY_FILE
    225                            : FILE_NON_DIRECTORY_FILE;
    226  NTSTATUS status = pNtCreateFile(
    227      &fileHandle, aAccess, &objectAttributes, &ioStatusBlock, nullptr, 0, 0,
    228      FILE_OPEN_IF, createOptions | FILE_SYNCHRONOUS_IO_NONALERT, nullptr, 0);
    229 
    230  if (fileHandle != INVALID_HANDLE_VALUE) {
    231    pNtClose(fileHandle);
    232  }
    233 
    234  nsCString accessString;
    235  if ((aAccess & FILE_GENERIC_READ) == FILE_GENERIC_READ) {
    236    accessString.AppendLiteral("r");
    237  }
    238  if ((aAccess & FILE_GENERIC_WRITE) == FILE_GENERIC_WRITE) {
    239    accessString.AppendLiteral("w");
    240  }
    241  if ((aAccess & FILE_GENERIC_EXECUTE) == FILE_GENERIC_EXECUTE) {
    242    accessString.AppendLiteral("e");
    243  }
    244 
    245  nsCString msgRelPath = NS_ConvertUTF16toUTF8(aRelativeFilePath);
    246  for (size_t i = 0, j = 0; i < aRelativeFilePath.Length(); ++i, ++j) {
    247    if (aRelativeFilePath[i] == u'\\') {
    248      msgRelPath.Insert('\\', j++);
    249    }
    250  }
    251 
    252  nsCString message;
    253  message.AppendPrintf(
    254      "Special dir: %s, file: %s, access: %s , returned status: %lx",
    255      aSpecialDirName, msgRelPath.get(), accessString.get(), status);
    256 
    257  aChild->SendReportTestResults(aName, aExpectSuccess == NT_SUCCESS(status),
    258                                message);
    259 }
    260 #endif
    261 
    262 #ifdef XP_MACOSX
    263 /*
    264 * Test if this process can launch another process with posix_spawnp,
    265 * exec, and LSOpenCFURLRef. All launches are expected to fail. In processes
    266 * where the sandbox permits reading of file metadata (content processes at
    267 * this time), we expect the posix_spawnp error to be EPERM. In processes
    268 * without that permission, we expect ENOENT. Changing the sandbox policy
    269 * may break this assumption, but the important aspect to test for is that the
    270 * launch is not permitted.
    271 */
    272 void RunMacTestLaunchProcess(SandboxTestingChild* child,
    273                             int aPosixSpawnExpectedError = ENOENT) {
    274  // Test that posix_spawnp fails
    275  char* argv[2];
    276  argv[0] = const_cast<char*>("bash");
    277  argv[1] = NULL;
    278  int rv = posix_spawnp(NULL, "/bin/bash", NULL, NULL, argv, NULL);
    279  nsPrintfCString posixSpawnMessage("posix_spawnp returned %d, expected %d", rv,
    280                                    aPosixSpawnExpectedError);
    281  child->SendReportTestResults("posix_spawnp test"_ns,
    282                               rv == aPosixSpawnExpectedError,
    283                               posixSpawnMessage);
    284 
    285  // Test that exec fails
    286  child->ErrnoTest("execv /bin/bash test"_ns, false, [&] {
    287    char* argvp = NULL;
    288    return execv("/bin/bash", &argvp);
    289  });
    290 
    291  // Test that launching an application using LSOpenCFURLRef fails
    292  char* uri = const_cast<char*>("/System/Applications/Utilities/Console.app");
    293  CFStringRef filePath = ::CFStringCreateWithCString(kCFAllocatorDefault, uri,
    294                                                     kCFStringEncodingUTF8);
    295  CFURLRef urlRef = ::CFURLCreateWithFileSystemPath(
    296      kCFAllocatorDefault, filePath, kCFURLPOSIXPathStyle, false);
    297  if (!urlRef) {
    298    child->SendReportTestResults("LSOpenCFURLRef"_ns, false,
    299                                 "CFURLCreateWithFileSystemPath failed"_ns);
    300    return;
    301  }
    302 
    303  OSStatus status = ApplicationServices::LSOpenCFURLRef(urlRef, NULL);
    304  ::CFRelease(urlRef);
    305  nsPrintfCString lsMessage(
    306      "LSOpenCFURLRef returned %d, "
    307      "expected kLSServerCommunicationErr (%d)",
    308      status, ApplicationServices::kLSServerCommunicationErr);
    309  child->SendReportTestResults(
    310      "LSOpenCFURLRef"_ns,
    311      status == ApplicationServices::kLSServerCommunicationErr, lsMessage);
    312 }
    313 
    314 /*
    315 * Test if this process can connect to the macOS window server.
    316 * When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
    317 * When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
    318 */
    319 void RunMacTestWindowServer(SandboxTestingChild* child,
    320                            bool aShouldHaveAccess = false) {
    321  // CGSessionCopyCurrentDictionary() returns NULL when a
    322  // connection to the window server is not available.
    323  CFDictionaryRef windowServerDict = CGSessionCopyCurrentDictionary();
    324  bool gotWindowServerDetails = (windowServerDict != nullptr);
    325  bool testPassed = (gotWindowServerDetails == aShouldHaveAccess);
    326  child->SendReportTestResults(
    327      "CGSessionCopyCurrentDictionary"_ns, testPassed,
    328      gotWindowServerDetails
    329          ? "dictionary returned, access is permitted"_ns
    330          : "no dictionary returned, access appears blocked"_ns);
    331  if (windowServerDict != nullptr) {
    332    CFRelease(windowServerDict);
    333  }
    334 }
    335 
    336 /*
    337 * Test if this process can get access to audio components on macOS.
    338 * When |aShouldHaveAccess| is true, the test passes if access is __permitted__.
    339 * When |aShouldHaveAccess| is false, the test passes if access is __blocked__.
    340 */
    341 void RunMacTestAudioAPI(SandboxTestingChild* child,
    342                        bool aShouldHaveAccess = false) {
    343  AudioStreamBasicDescription inputFormat;
    344  inputFormat.mFormatID = kAudioFormatMPEG4AAC;
    345  inputFormat.mSampleRate = 48000.0;
    346  inputFormat.mChannelsPerFrame = 2;
    347  inputFormat.mBitsPerChannel = 0;
    348  inputFormat.mFormatFlags = 0;
    349  inputFormat.mFramesPerPacket = 1024;
    350  inputFormat.mBytesPerPacket = 0;
    351 
    352  UInt32 inputFormatSize = sizeof(inputFormat);
    353  OSStatus status = AudioFormatGetProperty(
    354      kAudioFormatProperty_FormatInfo, 0, NULL, &inputFormatSize, &inputFormat);
    355 
    356  bool gotAudioFormat = (status == 0);
    357  bool testPassed = (gotAudioFormat == aShouldHaveAccess);
    358  child->SendReportTestResults(
    359      "AudioFormatGetProperty"_ns, testPassed,
    360      gotAudioFormat ? "got audio format, access is permitted"_ns
    361                     : "no audio format, access appears blocked"_ns);
    362 }
    363 #endif /* XP_MACOSX */
    364 
    365 #ifdef XP_WIN
    366 void RunWinTestWin32k(SandboxTestingChild* child,
    367                      bool aShouldHaveAccess = true) {
    368  bool isLockedDown = (IsWin32kLockedDown() == true);
    369  bool testPassed = (isLockedDown == aShouldHaveAccess);
    370  child->SendReportTestResults(
    371      "Win32kLockdown"_ns, testPassed,
    372      isLockedDown ? "got lockdown, access is blocked"_ns
    373                   : "no lockdown, access appears permitted"_ns);
    374 }
    375 #endif  // XP_WIN
    376 
    377 void RunTestsContent(SandboxTestingChild* child) {
    378  MOZ_ASSERT(child, "No SandboxTestingChild*?");
    379 
    380  RunGenericTests(child);
    381 
    382 #ifdef XP_UNIX
    383  struct stat st;
    384  static const char kAllowedPath[] = "/usr/lib";
    385 
    386  child->ErrnoTest("fstatat_as_stat"_ns, true,
    387                   [&] { return fstatat(AT_FDCWD, kAllowedPath, &st, 0); });
    388  child->ErrnoTest("fstatat_as_lstat"_ns, true, [&] {
    389    return fstatat(AT_FDCWD, kAllowedPath, &st, AT_SYMLINK_NOFOLLOW);
    390  });
    391 
    392 #  ifdef XP_LINUX
    393  child->ErrnoTest("fstatat_as_fstat"_ns, true,
    394                   [&] { return fstatat(0, "", &st, AT_EMPTY_PATH); });
    395 
    396  const struct timespec usec = {0, 1000};
    397  child->ErrnoTest("nanosleep"_ns, true,
    398                   [&] { return nanosleep(&usec, nullptr); });
    399 
    400  struct timespec res = {0, 0};
    401  child->ErrnoTest("clock_getres"_ns, true,
    402                   [&] { return clock_getres(CLOCK_REALTIME, &res); });
    403 
    404  // same process is allowed
    405  struct timespec tproc = {0, 0};
    406  clockid_t same_process = MAKE_PROCESS_CPUCLOCK(getpid(), CPUCLOCK_SCHED);
    407  child->ErrnoTest("clock_gettime_same_process"_ns, true,
    408                   [&] { return clock_gettime(same_process, &tproc); });
    409 
    410  // different process is blocked by sandbox (SIGSYS, kernel would return
    411  // EINVAL)
    412  struct timespec tprocd = {0, 0};
    413  clockid_t diff_process = MAKE_PROCESS_CPUCLOCK(1, CPUCLOCK_SCHED);
    414  child->ErrnoValueTest("clock_gettime_diff_process"_ns, ENOSYS,
    415                        [&] { return clock_gettime(diff_process, &tprocd); });
    416 
    417  // thread is allowed
    418  struct timespec tthread = {0, 0};
    419  clockid_t thread =
    420      MAKE_THREAD_CPUCLOCK((pid_t)syscall(__NR_gettid), CPUCLOCK_SCHED);
    421  child->ErrnoTest("clock_gettime_thread"_ns, true,
    422                   [&] { return clock_gettime(thread, &tthread); });
    423 
    424  // getcpu is allowed
    425  // We're using syscall directly because:
    426  // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
    427  // - getcpu isn't defined in the header files we're using yet.
    428  int c;
    429  child->ErrnoTest("getcpu"_ns, true,
    430                   [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
    431 
    432  // An abstract socket that does not starts with '/', so we don't want it to
    433  // work.
    434  // Checking ENETUNREACH should be thrown by SandboxBrokerClient::Connect()
    435  // when it detects it does not starts with a '/'
    436  child->ErrnoValueTest("connect_abstract_blocked"_ns, ENETUNREACH, [&] {
    437    int sockfd;
    438    struct sockaddr_un addr;
    439    char str[] = "\0xyz";  // Abstract socket requires first byte to be NULL
    440    size_t str_size = 4;
    441 
    442    memset(&addr, 0, sizeof(struct sockaddr_un));
    443    addr.sun_family = AF_UNIX;
    444    memcpy(&addr.sun_path, str, str_size);
    445 
    446    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    447    if (sockfd == -1) {
    448      return -1;
    449    }
    450 
    451    int con_st = connect(sockfd, (struct sockaddr*)&addr,
    452                         sizeof(sa_family_t) + str_size);
    453    return con_st;
    454  });
    455 
    456  // An abstract socket that does starts with /, so we do want it to work.
    457  //
    458  // Normally, this will be passed to the broker (unlike the previous
    459  // test) and rejected, failing with EACCES.
    460  //
    461  // With content sandbox level <5, the expected error is ECONNREFUSED
    462  // (the broker tries to connect but it fails at the OS level);
    463  // however, these tests don't handle non-default pref settings.
    464  child->ErrnoValueTest("connect_abstract_permit"_ns, EACCES, [&] {
    465    int sockfd;
    466    struct sockaddr_un addr;
    467    // we re-use actual X path, because this is what is allowed within
    468    // SandboxBrokerPolicyFactory::InitContentPolicy()
    469    // We can't just use any random path allowed, but one with CONNECT allowed.
    470 
    471    // (Note that the real X11 sockets have names like `X0` for
    472    // display `:0`; there shouldn't be anything named just `X`.)
    473 
    474    // Abstract socket requires first byte to be NULL
    475    char str[] = "\0/tmp/.X11-unix/X";
    476    size_t str_size = 17;
    477 
    478    memset(&addr, 0, sizeof(struct sockaddr_un));
    479    addr.sun_family = AF_UNIX;
    480    memcpy(&addr.sun_path, str, str_size);
    481 
    482    sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
    483    if (sockfd == -1) {
    484      return -1;
    485    }
    486 
    487    int con_st = connect(sockfd, (struct sockaddr*)&addr,
    488                         sizeof(sa_family_t) + str_size);
    489    return con_st;
    490  });
    491 
    492  // Testing FIPS-relevant files, which need to be accessible
    493  std::vector<std::pair<const char*, bool>> open_tests = {
    494      {"/dev/random", true}};
    495  // Not all systems have that file, so we only test access, if it exists
    496  // in the first place
    497  if (stat("/proc/sys/crypto/fips_enabled", &st) == 0) {
    498    open_tests.push_back({"/proc/sys/crypto/fips_enabled", true});
    499  }
    500 
    501  for (const std::pair<const char*, bool>& to_open : open_tests) {
    502    child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
    503                     to_open.second, [&] {
    504                       int fd = open(to_open.first, O_RDONLY);
    505                       if (to_open.second && fd > 0) {
    506                         close(fd);
    507                       }
    508                       return fd;
    509                     });
    510  }
    511 
    512  child->ErrnoTest("statfs"_ns, true, [] {
    513    struct statfs sf;
    514    return statfs("/usr/share", &sf);
    515  });
    516 
    517  child->ErrnoTest("pipe2"_ns, true, [] {
    518    int fds[2];
    519    int rv = pipe2(fds, O_CLOEXEC);
    520    int savedErrno = errno;
    521    if (rv == 0) {
    522      close(fds[0]);
    523      close(fds[1]);
    524    }
    525    errno = savedErrno;
    526    return rv;
    527  });
    528 
    529  child->ErrnoValueTest("chroot"_ns, ENOSYS, [] { return chroot("/"); });
    530 
    531  child->ErrnoValueTest("pipe2_notif"_ns, ENOSYS, [] {
    532    int fds[2];
    533    return pipe2(fds, O_NOTIFICATION_PIPE);
    534  });
    535 
    536 #    ifdef MOZ_X11
    537  // Check that X11 access is blocked (bug 1129492).
    538  // This will fail if security.sandbox.content.level is less than 5.
    539  if (PR_GetEnv("DISPLAY")) {
    540    Display* disp = XOpenDisplay(nullptr);
    541 
    542    child->SendReportTestResults(
    543        "x11_access"_ns, !disp,
    544        disp ? "XOpenDisplay succeeded"_ns : "XOpenDisplay failed"_ns);
    545    if (disp) {
    546      XCloseDisplay(disp);
    547    }
    548  }
    549 #    endif  // MOZ_X11
    550 
    551  child->ErrnoTest("realpath localtime"_ns, true, [] {
    552    char buf[PATH_MAX];
    553    return realpath("/etc/localtime", buf) ? 0 : -1;
    554  });
    555 
    556  // Check that readlink truncates results longer than the buffer
    557  // (rather than failing) and returns the total number of bytes
    558  // actually written (not the size of the link or anything else).
    559  {
    560    char buf;
    561    ssize_t rv = readlink("/etc/localtime", &buf, 1);
    562    int err = errno;
    563    if (rv == 1) {
    564      child->SendReportTestResults("readlink truncate"_ns, true,
    565                                   "expected 1, got 1"_ns);
    566    } else if (rv < 0) {
    567      nsPrintfCString msg("expected 1, got error: %s", strerror(err));
    568      child->SendReportTestResults("readlink truncate"_ns, false, msg);
    569    } else {
    570      nsPrintfCString msg("expected 1, got %zd", rv);
    571      child->SendReportTestResults("readlink truncate"_ns, false, msg);
    572    }
    573  }
    574 
    575  {
    576    static constexpr size_t kMapSize = 65536;
    577    void* mapping = mmap(nullptr, kMapSize, PROT_READ | PROT_WRITE,
    578                         MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    579    MOZ_ASSERT(mapping != MAP_FAILED);
    580    child->ErrnoTest("mremap-zero"_ns, true, [&] {
    581      void* rv = mremap(mapping, kMapSize, kMapSize, 0);
    582      if (rv == MAP_FAILED) {
    583        return -1;
    584      }
    585      MOZ_ASSERT(rv == mapping);
    586      return 0;
    587    });
    588 
    589    child->ErrnoValueTest("mremap-forbidden"_ns, ENOSYS, [&] {
    590      void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_DONTUNMAP);
    591      // This is an invalid flag combination (DONTUNMAP requires
    592      // MAYMOVE) so it will always fail with *something*.
    593      MOZ_ASSERT(rv == MAP_FAILED);
    594      return -1;
    595    });
    596 
    597    munmap(mapping, kMapSize);
    598  }
    599 
    600  child->ErrnoValueTest("ioctl_dma_buf"_ns, ENOSYS, [] {
    601    // Attempt an arbitrary non-tty ioctl, on the wrong type of fd; if
    602    // allowed it would fail with ENOTTY (see the RDD tests) but in
    603    // this sandbox it should be blocked (ENOSYS).
    604    return ioctl(0, _IOW('b', 0, uint64_t), nullptr);
    605  });
    606 
    607  child->ErrnoValueTest("send_with_flag"_ns, ENOSYS, [] {
    608    char c = 0;
    609    return send(0, &c, 1, MSG_CONFIRM);
    610  });
    611 
    612  child->ErrnoValueTest("mmap_huge"_ns, ENOSYS, [] {
    613    void* ptr =
    614        mmap(nullptr, 1 << 21, PROT_READ | PROT_WRITE,
    615             MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_HUGE_2MB, -1, 0);
    616    return ptr == MAP_FAILED ? -1 : 0;
    617  });
    618 
    619  child->ErrnoValueTest("memfd_huge"_ns, ENOSYS, [] {
    620    int fd = syscall(__NR_memfd_create, "hugemem",
    621                     MFD_CLOEXEC | MFD_HUGETLB | MFD_HUGE_2MB);
    622    if (fd >= 0) {
    623      close(fd);
    624    }
    625    // Returning a closed fd is fine; it's just going to be tested for >= 0.
    626    return fd;
    627  });
    628 
    629 #  endif  // XP_LINUX
    630 
    631 #  ifdef XP_MACOSX
    632  RunMacTestLaunchProcess(child, EPERM);
    633  RunMacTestWindowServer(child);
    634  RunMacTestAudioAPI(child, true);
    635 #  endif
    636 
    637 #elif XP_WIN
    638  FileTest("read from chrome"_ns, NS_APP_USER_CHROME_DIR, u"sandboxTest.txt"_ns,
    639           FILE_GENERIC_READ, true, child);
    640  FileTest("read from profile via relative path"_ns, NS_APP_USER_CHROME_DIR,
    641           u"..\\sandboxTest.txt"_ns, FILE_GENERIC_READ, false, child);
    642  // The profile dir is the parent of the chrome dir.
    643  FileTest("read from chrome using forward slash"_ns,
    644           NS_APP_USER_PROFILE_50_DIR, u"chrome/sandboxTest.txt"_ns,
    645           FILE_GENERIC_READ, false, child);
    646 
    647  // Note: these only pass in DEBUG builds because we allow write access to the
    648  // temp dir for certain test logs and that is where the profile is created.
    649  FileTest("read from profile"_ns, NS_APP_USER_PROFILE_50_DIR,
    650           u"sandboxTest.txt"_ns, FILE_GENERIC_READ, kIsDebug, child);
    651  FileTest("read/write from chrome"_ns, NS_APP_USER_CHROME_DIR,
    652           u"sandboxTest.txt"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE,
    653           kIsDebug, child);
    654 #else
    655    child->ReportNoTests();
    656 #endif
    657 }
    658 
    659 void RunTestsSocket(SandboxTestingChild* child) {
    660  MOZ_ASSERT(child, "No SandboxTestingChild*?");
    661 
    662  RunGenericTests(child);
    663 
    664 #ifdef XP_UNIX
    665  child->ErrnoTest("getaddrinfo"_ns, true, [&] {
    666    struct addrinfo* res;
    667    int rv = getaddrinfo("localhost", nullptr, nullptr, &res);
    668    if (res != nullptr) {
    669      freeaddrinfo(res);
    670    }
    671    return rv;
    672  });
    673 
    674 #  ifdef XP_LINUX
    675  child->ErrnoTest("prctl_allowed"_ns, true, [&] {
    676    int rv = prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);
    677    return rv;
    678  });
    679 
    680  child->ErrnoTest("prctl_blocked"_ns, false, [&] {
    681    int rv = prctl(PR_GET_SECCOMP, 0, 0, 0, 0);
    682    return rv;
    683  });
    684 
    685  // Testing FIPS-relevant files, which need to be accessible
    686  std::vector<std::pair<const char*, bool>> open_tests = {
    687      {"/dev/random", true}};
    688  // Not all systems have that file, so we only test access, if it exists
    689  // in the first place
    690  struct stat st;
    691  if (stat("/proc/sys/crypto/fips_enabled", &st) == 0) {
    692    open_tests.push_back({"/proc/sys/crypto/fips_enabled", true});
    693  }
    694 
    695  for (const std::pair<const char*, bool>& to_open : open_tests) {
    696    child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
    697                     to_open.second, [&] {
    698                       int fd = open(to_open.first, O_RDONLY);
    699                       if (to_open.second && fd > 0) {
    700                         close(fd);
    701                       }
    702                       return fd;
    703                     });
    704  }
    705 
    706  // getcpu is allowed
    707  // We're using syscall directly because:
    708  // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
    709  // - getcpu isn't defined in the header files we're using yet.
    710  int c;
    711  child->ErrnoTest("getcpu"_ns, true,
    712                   [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
    713 
    714  child->ErrnoTest("sendmsg"_ns, true, [&] {
    715    int fd = socket(AF_INET6, SOCK_DGRAM, 0);
    716    if (fd < 0) {
    717      return fd;
    718    }
    719 
    720    struct sockaddr_in6 addr;
    721    memset(&addr, 0, sizeof(addr));
    722    addr.sin6_family = AF_INET6;
    723    // Address within 100::/64, i.e. IPv6 discard prefix.
    724    inet_pton(AF_INET6, "100::1", &addr.sin6_addr);
    725    addr.sin6_port = htons(12345);
    726 
    727    struct msghdr msg = {0};
    728    struct iovec iov[1];
    729    char buf[] = "test";
    730    iov[0].iov_base = buf;
    731    iov[0].iov_len = sizeof(buf);
    732    msg.msg_iov = iov;
    733    msg.msg_iovlen = 1;
    734    msg.msg_name = &addr;
    735    msg.msg_namelen = sizeof(addr);
    736 
    737    int rv = sendmsg(fd, &msg, 0);
    738    close(fd);
    739    MOZ_ASSERT(rv == sizeof(buf),
    740               "Expected sendmsg to return the number of bytes sent");
    741    return rv;
    742  });
    743 
    744  child->ErrnoTest("recvmmsg"_ns, true, [&] {
    745    int fd = socket(AF_INET6, SOCK_DGRAM, 0);
    746    if (fd < 0) {
    747      return fd;
    748    }
    749 
    750    // Set the socket to non-blocking mode
    751    int flags = fcntl(fd, F_GETFL, 0);
    752    if (flags < 0) {
    753      close(fd);
    754      return -1;
    755    }
    756    if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
    757      close(fd);
    758      return -1;
    759    }
    760 
    761    struct sockaddr_in6 addr;
    762    memset(&addr, 0, sizeof(addr));
    763    addr.sin6_family = AF_INET6;
    764    addr.sin6_addr = in6addr_any;
    765    addr.sin6_port = htons(0);
    766 
    767    if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    768      close(fd);
    769      return -1;
    770    }
    771 
    772    struct mmsghdr msgs[1];
    773    memset(msgs, 0, sizeof(msgs));
    774    struct iovec iov[1];
    775    char buf[64];
    776    iov[0].iov_base = buf;
    777    iov[0].iov_len = sizeof(buf);
    778    msgs[0].msg_hdr.msg_iov = iov;
    779    msgs[0].msg_hdr.msg_iovlen = 1;
    780 
    781    int rv = recvmmsg(fd, msgs, 1, 0, nullptr);
    782    close(fd);
    783    MOZ_ASSERT(rv == -1 && errno == EAGAIN,
    784               "recvmmsg should return -1 with EAGAIN given that no datagrams "
    785               "are available");
    786    return 0;
    787  });
    788 
    789  child->ErrnoValueTest("send_with_flag"_ns, ENOSYS, [] {
    790    char c = 0;
    791    return send(0, &c, 1, MSG_OOB);
    792  });
    793 
    794  child->ErrnoValueTest("ioctl_dma_buf"_ns, ENOSYS, [] {
    795    // Attempt an arbitrary non-tty ioctl, on the wrong type of fd; if
    796    // allowed it would fail with ENOTTY (see the RDD tests) but in
    797    // this sandbox it should be blocked (ENOSYS).
    798    return ioctl(0, _IOW('b', 0, uint64_t), nullptr);
    799  });
    800 #  endif  // XP_LINUX
    801 #elif XP_MACOSX
    802  RunMacTestLaunchProcess(child);
    803  RunMacTestWindowServer(child);
    804  RunMacTestAudioAPI(child);
    805 #else   // XP_UNIX
    806    child->ReportNoTests();
    807 #endif  // XP_UNIX
    808 }
    809 
    810 void RunTestsRDD(SandboxTestingChild* child) {
    811  MOZ_ASSERT(child, "No SandboxTestingChild*?");
    812 
    813  RunGenericTests(child);
    814 
    815 #ifdef XP_UNIX
    816 #  ifdef XP_LINUX
    817  child->ErrnoValueTest("ioctl_tiocsti"_ns, ENOSYS, [&] {
    818    int rv = ioctl(1, TIOCSTI, "x");
    819    return rv;
    820  });
    821 
    822  struct rusage res = {};
    823  child->ErrnoTest("getrusage"_ns, true, [&] {
    824    int rv = getrusage(RUSAGE_SELF, &res);
    825    return rv;
    826  });
    827 
    828  child->ErrnoValueTest("unlink"_ns, ENOENT, [&] {
    829    int rv = unlink("");
    830    return rv;
    831  });
    832 
    833  child->ErrnoValueTest("unlinkat"_ns, ENOENT, [&] {
    834    int rv = unlinkat(AT_FDCWD, "", 0);
    835    return rv;
    836  });
    837 
    838  RunTestsSched(child);
    839 
    840  child->ErrnoValueTest("socket_inet"_ns, EACCES,
    841                        [] { return socket(AF_INET, SOCK_STREAM, 0); });
    842 
    843  child->ErrnoValueTest("socket_unix"_ns, EACCES,
    844                        [] { return socket(AF_UNIX, SOCK_STREAM, 0); });
    845 
    846  child->ErrnoTest("uname"_ns, true, [] {
    847    struct utsname uts;
    848    return uname(&uts);
    849  });
    850 
    851  child->ErrnoValueTest("ioctl_dma_buf"_ns, ENOTTY, [] {
    852    // Apply the ioctl to the wrong kind of fd; it should fail with
    853    // ENOTTY (rather than ENOSYS if it were blocked).
    854    return ioctl(0, _IOW('b', 0, uint64_t), nullptr);
    855  });
    856 
    857  // getcpu is allowed
    858  // We're using syscall directly because:
    859  // - sched_getcpu uses vdso and as a result doesn't go through the sandbox.
    860  // - getcpu isn't defined in the header files we're using yet.
    861  int c;
    862  child->ErrnoTest("getcpu"_ns, true,
    863                   [&] { return syscall(SYS_getcpu, &c, NULL, NULL); });
    864 
    865  // The nvidia proprietary drivers will, in some cases, try to
    866  // mknod their device files; we reject this politely.
    867  child->ErrnoValueTest("mknod"_ns, EPERM, [] {
    868    return mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3));
    869  });
    870 
    871  // Rust panics call getcwd to try to print relative paths in
    872  // backtraces.
    873  child->ErrnoValueTest("getcwd"_ns, ENOENT, [] {
    874    char buf[4096];
    875    return (getcwd(buf, sizeof(buf)) == nullptr) ? -1 : 0;
    876  });
    877 
    878  // nvidia defines some ioctls with the type 0x46 ('F', otherwise
    879  // used by fbdev) and numbers starting from 200 (0xc8).
    880  child->ErrnoValueTest("ioctl_nvidia"_ns, ENOTTY,
    881                        [] { return ioctl(0, 0x46c8, nullptr); });
    882 
    883  child->ErrnoTest("statfs"_ns, true, [] {
    884    struct statfs sf;
    885    return statfs("/usr/share", &sf);
    886  });
    887 
    888  child->ErrnoValueTest("fork"_ns, EPERM, [] {
    889    pid_t pid = fork();
    890    if (pid == 0) {
    891      // Success: shouldn't happen, and parent will report a test
    892      // failure.
    893      _exit(0);
    894    }
    895    return pid;
    896  });
    897 
    898 #  elif XP_MACOSX
    899  RunMacTestLaunchProcess(child);
    900  RunMacTestWindowServer(child);
    901  RunMacTestAudioAPI(child, true);
    902 #  endif
    903 #else  // XP_UNIX
    904 #  ifdef XP_WIN
    905  RunWinTestWin32k(child, false);
    906 #  endif  // XP_WIN
    907  child->ReportNoTests();
    908 #endif
    909 }
    910 
    911 void RunTestsGMPlugin(SandboxTestingChild* child) {
    912  MOZ_ASSERT(child, "No SandboxTestingChild*?");
    913 
    914  RunGenericTests(child, /* aIsGMP = */ true);
    915 
    916 #ifdef XP_UNIX
    917 #  ifdef XP_LINUX
    918  struct utsname utsname_res = {};
    919  child->ErrnoTest("uname_restricted"_ns, true, [&] {
    920    int rv = uname(&utsname_res);
    921 
    922    nsCString expectedSysname("Linux"_ns);
    923    nsCString sysname(utsname_res.sysname);
    924    nsCString expectedVersion("3"_ns);
    925    nsCString version(utsname_res.version);
    926    if ((sysname != expectedSysname) || (version != expectedVersion)) {
    927      return -1;
    928    }
    929 
    930    return rv;
    931  });
    932 
    933  child->ErrnoTest("getuid"_ns, true, [&] { return getuid(); });
    934  child->ErrnoTest("getgid"_ns, true, [&] { return getgid(); });
    935  child->ErrnoTest("geteuid"_ns, true, [&] { return geteuid(); });
    936  child->ErrnoTest("getegid"_ns, true, [&] { return getegid(); });
    937 
    938  RunTestsSched(child);
    939 
    940  std::vector<std::pair<const char*, bool>> open_tests = {
    941      {"/etc/ld.so.cache", true},
    942      {"/proc/cpuinfo", true},
    943      {"/etc/hostname", false}};
    944 
    945  for (const std::pair<const char*, bool>& to_open : open_tests) {
    946    child->ErrnoTest("open("_ns + nsCString(to_open.first) + ")"_ns,
    947                     to_open.second, [&] {
    948                       int fd = open(to_open.first, O_RDONLY);
    949                       if (to_open.second && fd > 0) {
    950                         close(fd);
    951                       }
    952                       return fd;
    953                     });
    954  }
    955 
    956  child->ErrnoValueTest("readlink_exe"_ns, EINVAL, [] {
    957    char pathBuf[PATH_MAX];
    958    return readlink("/proc/self/exe", pathBuf, sizeof(pathBuf));
    959  });
    960 
    961  child->ErrnoTest("memfd_sizing"_ns, true, [] {
    962    int fd = syscall(__NR_memfd_create, "sandbox-test", 0);
    963    if (fd < 0) {
    964      if (errno == ENOSYS) {
    965        // Don't fail the test if the kernel is old.
    966        return 0;
    967      }
    968      return -1;
    969    }
    970 
    971    int rv = ftruncate(fd, 4096);
    972    int savedErrno = errno;
    973    close(fd);
    974    errno = savedErrno;
    975    return rv;
    976  });
    977 
    978  {
    979    static constexpr size_t kMapSize = 65536;
    980    void* mapping = mmap(nullptr, kMapSize, PROT_READ | PROT_WRITE,
    981                         MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
    982    MOZ_ASSERT(mapping != MAP_FAILED);
    983 
    984 #    ifndef MOZ_MEMORY
    985    child->ErrnoTest("mremap-move"_ns, true, [&] {
    986      void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_MAYMOVE);
    987      if (rv == MAP_FAILED) {
    988        return -1;
    989      }
    990      // It *may* move the mapping, but when the size doesn't change
    991      // it's not expected to:
    992      MOZ_ASSERT(rv == mapping);
    993      return 0;
    994    });
    995 #    endif
    996 
    997    child->ErrnoValueTest("mremap-forbidden"_ns, ENOSYS, [&] {
    998      void* rv = mremap(mapping, kMapSize, kMapSize, MREMAP_DONTUNMAP);
    999      // This is an invalid flag combination (DONTUNMAP requires
   1000      // MAYMOVE) so it will always fail with *something*.
   1001      MOZ_ASSERT(rv == MAP_FAILED);
   1002      return -1;
   1003    });
   1004 
   1005    munmap(mapping, kMapSize);
   1006  }
   1007 
   1008 #  elif XP_MACOSX  // XP_LINUX
   1009  RunMacTestLaunchProcess(child);
   1010  /* The Mac GMP process requires access to the window server */
   1011  RunMacTestWindowServer(child, true /* aShouldHaveAccess */);
   1012  RunMacTestAudioAPI(child);
   1013 #  endif           // XP_MACOSX
   1014 #else              // XP_UNIX
   1015  child->ReportNoTests();
   1016 #endif
   1017 }
   1018 
   1019 void RunTestsGenericUtility(SandboxTestingChild* child) {
   1020  MOZ_ASSERT(child, "No SandboxTestingChild*?");
   1021 
   1022  RunGenericTests(child);
   1023 
   1024 #ifdef XP_UNIX
   1025 #  ifdef XP_LINUX
   1026  child->ErrnoValueTest("ioctl_tiocsti"_ns, ENOSYS, [&] {
   1027    int rv = ioctl(1, TIOCSTI, "x");
   1028    return rv;
   1029  });
   1030 
   1031  child->ErrnoTest("getrusage"_ns, true, [&] {
   1032    struct rusage res;
   1033    return getrusage(RUSAGE_SELF, &res);
   1034  });
   1035 
   1036  child->ErrnoTest("uname"_ns, true, [&] {
   1037    struct utsname uts;
   1038    return uname(&uts);
   1039  });
   1040 
   1041 #  elif XP_MACOSX  // XP_LINUX
   1042  RunMacTestLaunchProcess(child);
   1043  RunMacTestWindowServer(child);
   1044  RunMacTestAudioAPI(child);
   1045 #  endif           // XP_MACOSX
   1046 #elif XP_WIN       // XP_UNIX
   1047  child->ErrnoValueTest("write_only"_ns, EACCES, [&] {
   1048    FILE* rv = fopen("test_sandbox.txt", "w");
   1049    if (rv != nullptr) {
   1050      fclose(rv);
   1051      return 0;
   1052    }
   1053    return -1;
   1054  });
   1055  RunWinTestWin32k(child);
   1056 #else              // XP_UNIX
   1057    child->ReportNoTests();
   1058 #endif             // XP_MACOSX
   1059 }
   1060 
   1061 void RunTestsUtilityMediaService(SandboxTestingChild* child,
   1062                                 ipc::SandboxingKind aSandbox) {
   1063  MOZ_ASSERT(child, "No SandboxTestingChild*?");
   1064 
   1065  RunGenericTests(child);
   1066 
   1067 #ifdef XP_UNIX
   1068 #  ifdef XP_LINUX
   1069  // getrusage is allowed in Generic Utility and on AudioDecoder
   1070  struct rusage res;
   1071  child->ErrnoTest("getrusage"_ns, true, [&] {
   1072    int rv = getrusage(RUSAGE_SELF, &res);
   1073    return rv;
   1074  });
   1075 
   1076  // get_mempolicy is not allowed in Generic Utility but is on AudioDecoder
   1077  child->ErrnoTest("get_mempolicy"_ns, true, [&] {
   1078    int numa_node;
   1079    int test_val = 0;
   1080    // <numaif.h> not installed by default, let's call directly the syscall
   1081    long rv = syscall(SYS_get_mempolicy, &numa_node, NULL, 0, (void*)&test_val,
   1082                      MPOL_F_NODE | MPOL_F_ADDR);
   1083    return rv;
   1084  });
   1085  // set_mempolicy is not allowed in Generic Utility but is on AudioDecoder
   1086  child->ErrnoValueTest("set_mempolicy"_ns, ENOSYS, [&] {
   1087    // <numaif.h> not installed by default, let's call directly the syscall
   1088    long rv = syscall(SYS_set_mempolicy, 0, NULL, 0);
   1089    return rv;
   1090  });
   1091 
   1092  child->ErrnoValueTest("prctl_capbtest_read_blocked"_ns, EINVAL, [] {
   1093    int rv = prctl(PR_CAPBSET_READ, 0, 0, 0, 0);
   1094    return rv;
   1095  });
   1096 
   1097 #  elif XP_MACOSX  // XP_LINUX
   1098  RunMacTestLaunchProcess(child);
   1099  RunMacTestWindowServer(child);
   1100  RunMacTestAudioAPI(
   1101      child,
   1102      aSandbox == ipc::SandboxingKind::UTILITY_AUDIO_DECODING_APPLE_MEDIA);
   1103 #  endif           // XP_MACOSX
   1104 #else              // XP_UNIX
   1105 #  ifdef XP_WIN
   1106  RunWinTestWin32k(child);
   1107 #  endif  // XP_WIN
   1108  child->ReportNoTests();
   1109 #endif    // XP_UNIX
   1110 }
   1111 
   1112 void RunTestsGPU(SandboxTestingChild* child) {
   1113  MOZ_ASSERT(child, "No SandboxTestingChild*?");
   1114 
   1115  RunGenericTests(child);
   1116 
   1117 #if defined(XP_WIN)
   1118 
   1119  FileTest("R/W access to shader-cache dir"_ns, NS_APP_USER_PROFILE_50_DIR,
   1120           u"shader-cache\\"_ns, FILE_GENERIC_READ | FILE_GENERIC_WRITE, true,
   1121           child);
   1122 
   1123  FileTest("R/W access to shader-cache files"_ns, NS_APP_USER_PROFILE_50_DIR,
   1124           u"shader-cache\\sandboxTest.txt"_ns,
   1125           FILE_GENERIC_READ | FILE_GENERIC_WRITE, true, child);
   1126 
   1127 #elif defined(XP_MACOSX)
   1128 
   1129  // Check if the GPU process sandbox has been started
   1130  bool isSandboxStarted = sandbox_check(getpid(), NULL, 0) == 1;
   1131  nsCString gpuSandboxCheckMessage;
   1132  if (isSandboxStarted) {
   1133    gpuSandboxCheckMessage.AppendLiteral(
   1134        "sandbox_check() indicates GPU process sandbox is running");
   1135  } else {
   1136    gpuSandboxCheckMessage.AppendLiteral(
   1137        "sandbox_check() indicates GPU process sandbox is not running");
   1138  }
   1139  child->SendReportTestResults("sandbox_check()"_ns, isSandboxStarted,
   1140                               gpuSandboxCheckMessage);
   1141 
   1142  // Home directory tests
   1143  const char* home = getenv("HOME");
   1144  if (home) {
   1145    // Test write to home directory
   1146    nsCString testFile(home);
   1147    testFile.Append("/gpu_sbox_writetest.tmp");
   1148 
   1149    child->ErrnoTest("write denied ($HOME)"_ns, false,
   1150                     [p = std::string(testFile.get())] {
   1151                       int fd = open(p.c_str(), O_CREAT | O_WRONLY, 0600);
   1152                       if (fd >= 0) {
   1153                         close(fd);
   1154                       }
   1155                       return fd;
   1156                     });
   1157 
   1158    // Test reading from home directory - file already created by parent process
   1159    nsCString testReadFile(home);
   1160    testReadFile.Append("/.mozilla_gpu_sandbox_read_test");
   1161    std::string path = std::string(testReadFile.get());
   1162    if (access(path.c_str(), F_OK) == 0) {
   1163      child->ErrnoTest("read denied (home test file)"_ns, false, [path] {
   1164        int fd = open(path.c_str(), O_RDONLY);
   1165        if (fd >= 0) {
   1166          close(fd);
   1167        }
   1168        return fd;
   1169      });
   1170    } else {
   1171      child->SendReportTestResults(
   1172          "read denied (home test file)"_ns, false,
   1173          "Test file does not exist, test setup failure"_ns);
   1174    }
   1175  } else {
   1176    child->SendReportTestResults("HOME check"_ns, false,
   1177                                 "HOME environment variable not set"_ns);
   1178  }
   1179 
   1180  // System directory
   1181  child->ErrnoTest("write denied (/tmp)"_ns, false, [] {
   1182    int fd = open("/tmp/gpu_sandbox_test_file.txt", O_CREAT | O_WRONLY, 0600);
   1183    if (fd >= 0) {
   1184      close(fd);
   1185    }
   1186    return fd;
   1187  });
   1188 
   1189  child->ErrnoTest("write denied (/private/tmp)"_ns, false, [] {
   1190    int fd = open("/private/tmp/sandboxTest.txt", O_CREAT | O_WRONLY, 0600);
   1191    if (fd >= 0) {
   1192      close(fd);
   1193    }
   1194    return fd;
   1195  });
   1196 
   1197  // Shader cache tests
   1198  char buf[PATH_MAX];
   1199  if (confstr(_CS_DARWIN_USER_CACHE_DIR, buf, sizeof(buf)) > 0) {
   1200    nsCString cache(buf);
   1201 
   1202    // Can't create directories at the cache root.
   1203    nsCString subdir = cache + "/mozilla-gpu-sbox-test"_ns;
   1204    child->ErrnoTest("mkdir denied (cache root subdir)"_ns, false,
   1205                     [s = std::string(subdir.get())] {
   1206                       int rv = mkdir(s.c_str(), 0700);
   1207                       if (rv == 0) {
   1208                         rmdir(s.c_str());  // cleanup if somehow created
   1209                       }
   1210                       return rv;
   1211                     });
   1212 
   1213    // Can't write files at the cache root.
   1214    nsCString file = cache + "/gpu_test_file.txt"_ns;
   1215    child->ErrnoTest("write denied (cache root)"_ns, false,
   1216                     [f = std::string(file.get())] {
   1217                       int fd = open(f.c_str(), O_CREAT | O_WRONLY, 0600);
   1218                       if (fd >= 0) {
   1219                         close(fd);
   1220                         unlink(f.c_str());
   1221                       }
   1222                       return fd;
   1223                     });
   1224 
   1225    // Can't create a fake GPU bundle cache directory.
   1226    nsCString fakeGpuDir = cache + "/org.mozilla.firefox-fake-gpu"_ns;
   1227    child->ErrnoTest("mkdir denied (fake GPU cache dir)"_ns, false,
   1228                     [dir = std::string(fakeGpuDir.get())] {
   1229                       int rv = mkdir(dir.c_str(), 0700);
   1230                       if (rv == 0) {
   1231                         rmdir(dir.c_str());  // cleanup if somehow created
   1232                       }
   1233                       return rv;
   1234                     });
   1235 
   1236    // Actual GPU bundle cache directory
   1237    nsCString actualGpuCacheDir =
   1238        cache + "/"_ns + nsCString(MOZ_GPU_PROCESS_BUNDLEID);
   1239 
   1240    // Allowed to write a regular file in the GPU bundle cache.
   1241    nsCString legitGpuFile = actualGpuCacheDir + "/gpu_test.cache"_ns;
   1242    child->ErrnoTest("write allowed (GPU bundle cache)"_ns, true,
   1243                     [f = std::string(legitGpuFile.get())] {
   1244                       int fd = open(f.c_str(), O_CREAT | O_WRONLY, 0600);
   1245                       if (fd >= 0) {
   1246                         close(fd);
   1247                         unlink(f.c_str());  // cleanup
   1248                         return 0;
   1249                       }
   1250                       return fd;
   1251                     });
   1252 
   1253    // Symlink test inside the GPU bundle cache.
   1254    nsCString symlinkInGpuCache = actualGpuCacheDir + "/bad_symlink"_ns;
   1255    child->ErrnoTest("symlink denied (GPU bundle cache)"_ns, false,
   1256                     [s = std::string(symlinkInGpuCache.get())] {
   1257                       int rv = symlink("/etc/passwd", s.c_str());
   1258                       if (rv == 0) {  // cleanup if somehow created
   1259                         int fd = open(s.c_str(), O_RDONLY);
   1260                         if (fd >= 0) {
   1261                           close(fd);
   1262                         }
   1263                         unlink(s.c_str());
   1264                       }
   1265                       return rv;
   1266                     });
   1267 
   1268  } else {
   1269    child->SendReportTestResults("cache dir check"_ns, false,
   1270                                 "Could not get user cache directory"_ns);
   1271  }
   1272 
   1273  // Test read permissions
   1274  child->ErrnoTest(
   1275      "read allowed (/System/.../SystemVersion.plist)"_ns, true, [] {
   1276        int fd =
   1277            open("/System/Library/CoreServices/SystemVersion.plist", O_RDONLY);
   1278        if (fd >= 0) {
   1279          close(fd);
   1280          return 0;
   1281        }
   1282        return fd;
   1283      });
   1284 
   1285  // Test directory listing permissions
   1286  child->ErrnoTest("list allowed (/Library/ColorSync/Profiles)"_ns, true, [] {
   1287    DIR* d = opendir("/Library/ColorSync/Profiles");
   1288    if (!d) return -1;
   1289    struct dirent* entry = readdir(d);
   1290    closedir(d);
   1291    return entry ? 0 : -1;
   1292  });
   1293 
   1294  child->ErrnoTest("list allowed (/private/var/db/CVMS)"_ns, true, [] {
   1295    DIR* d = opendir("/private/var/db/CVMS");
   1296    if (!d) return -1;
   1297    struct dirent* entry = readdir(d);
   1298    closedir(d);
   1299    return entry ? 0 : -1;
   1300  });
   1301 
   1302  // Test sysctl permissions
   1303  child->ErrnoTest("sysctl allowed (kern.ostype)"_ns, true, [] {
   1304    char buf[256];
   1305    size_t sz = sizeof(buf);
   1306    int rv = sysctlbyname("kern.ostype", buf, &sz, nullptr, 0);
   1307    return rv;
   1308  });
   1309 
   1310  child->ErrnoTest("sysctl allowed (hw.memsize)"_ns, true, [] {
   1311    uint64_t mem = 0;
   1312    size_t sz = sizeof(mem);
   1313    int rv = sysctlbyname("hw.memsize", &mem, &sz, nullptr, 0);
   1314    return rv;
   1315  });
   1316 
   1317  // System directory write
   1318  child->ErrnoTest("write denied (root)"_ns, false, [] {
   1319    int fd = open("/gpu_root_test.txt", O_CREAT | O_WRONLY, 0600);
   1320    if (fd >= 0) {
   1321      close(fd);
   1322    }
   1323    return fd;
   1324  });
   1325 
   1326  child->ErrnoTest("write denied (/usr/local)"_ns, false, [] {
   1327    int fd = open("/usr/local/gpu_bad_test.txt", O_CREAT | O_WRONLY, 0600);
   1328    if (fd >= 0) {
   1329      close(fd);
   1330    }
   1331    return fd;
   1332  });
   1333 
   1334  child->ErrnoTest("write denied (/etc)"_ns, false, [] {
   1335    int fd = open("/etc/gpu_bad_test.txt", O_CREAT | O_WRONLY, 0600);
   1336    if (fd >= 0) {
   1337      close(fd);
   1338    }
   1339    return fd;
   1340  });
   1341 
   1342  RunMacTestLaunchProcess(child, EPERM);
   1343  RunMacTestAudioAPI(child);
   1344  RunMacTestWindowServer(child, true);
   1345 
   1346 #else   // defined(XP_WIN)
   1347    child->ReportNoTests();
   1348 #endif  // defined(XP_WIN)
   1349 }
   1350 
   1351 }  // namespace mozilla