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, ¶m_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), ¶m_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), ¶m_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