TestBroker.cpp (22980B)
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 file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "gtest/gtest.h" 8 9 #include "broker/SandboxBroker.h" 10 #include "broker/SandboxBrokerUtils.h" 11 #include "SandboxBrokerClient.h" 12 13 #include <errno.h> 14 #include <fcntl.h> 15 #include <pthread.h> 16 #include <stdlib.h> 17 #include <sched.h> 18 #include <semaphore.h> 19 #include <sys/resource.h> 20 #include <sys/stat.h> 21 #include <sys/types.h> 22 #include <time.h> 23 #include <unistd.h> 24 25 #include "mozilla/Atomics.h" 26 #include "mozilla/PodOperations.h" 27 #include "mozilla/UniquePtr.h" 28 #include "mozilla/ipc/FileDescriptor.h" 29 #include "nsIThreadManager.h" 30 31 namespace mozilla { 32 33 class SandboxBrokerTest : public ::testing::Test { 34 static const int MAY_ACCESS = SandboxBroker::MAY_ACCESS; 35 static const int MAY_READ = SandboxBroker::MAY_READ; 36 static const int MAY_WRITE = SandboxBroker::MAY_WRITE; 37 static const int MAY_CREATE = SandboxBroker::MAY_CREATE; 38 static const auto AddAlways = SandboxBroker::Policy::AddAlways; 39 40 RefPtr<SandboxBroker> mServer; 41 UniquePtr<SandboxBrokerClient> mClient; 42 43 UniquePtr<const SandboxBroker::Policy> GetPolicy() const; 44 45 template <class C, void (C::*Main)()> 46 static void* ThreadMain(void* arg) { 47 (static_cast<C*>(arg)->*Main)(); 48 return nullptr; 49 } 50 51 protected: 52 int Open(const char* aPath, int aFlags) { 53 return mClient->Open(aPath, aFlags); 54 } 55 int Access(const char* aPath, int aMode) { 56 return mClient->Access(aPath, aMode); 57 } 58 int Stat(const char* aPath, statstruct* aStat) { 59 return mClient->Stat(aPath, aStat); 60 } 61 int LStat(const char* aPath, statstruct* aStat) { 62 return mClient->LStat(aPath, aStat); 63 } 64 int Chmod(const char* aPath, int aMode) { 65 return mClient->Chmod(aPath, aMode); 66 } 67 int Link(const char* aPath, const char* bPath) { 68 return mClient->Link(aPath, bPath); 69 } 70 int Mkdir(const char* aPath, int aMode) { 71 return mClient->Mkdir(aPath, aMode); 72 } 73 int Symlink(const char* aPath, const char* bPath) { 74 return mClient->Symlink(aPath, bPath); 75 } 76 int Rename(const char* aPath, const char* bPath) { 77 return mClient->Rename(aPath, bPath); 78 } 79 int Rmdir(const char* aPath) { return mClient->Rmdir(aPath); } 80 int Unlink(const char* aPath) { return mClient->Unlink(aPath); } 81 ssize_t Readlink(const char* aPath, char* aBuff, size_t aSize) { 82 return mClient->Readlink(aPath, aBuff, aSize); 83 } 84 85 void SetUp() override { 86 ipc::FileDescriptor fd; 87 88 mServer = SandboxBroker::Create(GetPolicy(), getpid(), fd); 89 ASSERT_NE(mServer, nullptr); 90 ASSERT_TRUE(fd.IsValid()); 91 auto rawFD = fd.TakePlatformHandle(); 92 mClient.reset(new SandboxBrokerClient(rawFD.release())); 93 } 94 95 template <class C, void (C::*Main)()> 96 void StartThread(pthread_t* aThread) { 97 ASSERT_EQ(0, pthread_create(aThread, nullptr, ThreadMain<C, Main>, 98 static_cast<C*>(this))); 99 } 100 101 template <class C, void (C::*Main)()> 102 void RunOnManyThreads() { 103 static const int kNumThreads = 5; 104 pthread_t threads[kNumThreads]; 105 for (pthread_t& thread : threads) { 106 StartThread<C, Main>(&thread); 107 } 108 for (pthread_t thread : threads) { 109 void* retval; 110 ASSERT_EQ(pthread_join(thread, &retval), 0); 111 ASSERT_EQ(retval, static_cast<void*>(nullptr)); 112 } 113 } 114 115 public: 116 void MultiThreadOpenWorker(); 117 void MultiThreadStatWorker(); 118 }; 119 120 UniquePtr<const SandboxBroker::Policy> SandboxBrokerTest::GetPolicy() const { 121 UniquePtr<SandboxBroker::Policy> policy(new SandboxBroker::Policy()); 122 123 policy->AddPath(MAY_READ | MAY_WRITE, "/dev/null", AddAlways); 124 policy->AddPath(MAY_READ, "/dev/zero", AddAlways); 125 policy->AddPath(MAY_READ, "/var/empty/qwertyuiop", AddAlways); 126 policy->AddPath(MAY_ACCESS, "/proc/self", 127 AddAlways); // Warning: Linux-specific. 128 policy->AddPath(MAY_READ | MAY_WRITE, "/tmp", AddAlways); 129 policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublu", AddAlways); 130 policy->AddPath(MAY_READ | MAY_WRITE | MAY_CREATE, "/tmp/blublublu", 131 AddAlways); 132 // This should be non-writable by the user running the test: 133 policy->AddPath(MAY_READ | MAY_WRITE, "/etc", AddAlways); 134 135 return std::move(policy); 136 } 137 138 TEST_F(SandboxBrokerTest, OpenForRead) { 139 int fd; 140 141 fd = Open("/dev/null", O_RDONLY); 142 ASSERT_GE(fd, 0) << "Opening /dev/null failed."; 143 close(fd); 144 fd = Open("/dev/zero", O_RDONLY); 145 ASSERT_GE(fd, 0) << "Opening /dev/zero failed."; 146 close(fd); 147 fd = Open("/var/empty/qwertyuiop", O_RDONLY); 148 EXPECT_EQ(-ENOENT, fd) << "Opening allowed but nonexistent file succeeded."; 149 fd = Open("/proc/self", O_RDONLY); 150 EXPECT_EQ(-EACCES, fd) << "Opening stat-only file for read succeeded."; 151 fd = Open("/proc/self/stat", O_RDONLY); 152 EXPECT_EQ(-EACCES, fd) << "Opening disallowed file succeeded."; 153 } 154 155 TEST_F(SandboxBrokerTest, OpenForWrite) { 156 int fd; 157 158 fd = Open("/dev/null", O_WRONLY); 159 ASSERT_GE(fd, 0) << "Opening /dev/null write-only failed."; 160 close(fd); 161 fd = Open("/dev/null", O_RDWR); 162 ASSERT_GE(fd, 0) << "Opening /dev/null read/write failed."; 163 close(fd); 164 fd = Open("/dev/zero", O_WRONLY); 165 ASSERT_EQ(-EACCES, fd) 166 << "Opening read-only-by-policy file write-only succeeded."; 167 fd = Open("/dev/zero", O_RDWR); 168 ASSERT_EQ(-EACCES, fd) 169 << "Opening read-only-by-policy file read/write succeeded."; 170 } 171 172 TEST_F(SandboxBrokerTest, SimpleRead) { 173 int fd; 174 char c; 175 176 fd = Open("/dev/null", O_RDONLY); 177 ASSERT_GE(fd, 0); 178 EXPECT_EQ(0, read(fd, &c, 1)); 179 close(fd); 180 fd = Open("/dev/zero", O_RDONLY); 181 ASSERT_GE(fd, 0); 182 ASSERT_EQ(1, read(fd, &c, 1)); 183 EXPECT_EQ(c, '\0'); 184 } 185 186 TEST_F(SandboxBrokerTest, BadFlags) { 187 int fd; 188 189 fd = Open("/dev/null", O_RDWR | O_ASYNC); 190 EXPECT_EQ(-EACCES, fd) << "O_ASYNC is banned."; 191 192 fd = Open("/dev/null", O_RDWR | 0x40000000); 193 EXPECT_EQ(-EACCES, fd) << "Unknown flag 0x40000000 is banned."; 194 } 195 196 TEST_F(SandboxBrokerTest, Access) { 197 EXPECT_EQ(0, Access("/dev/null", F_OK)); 198 EXPECT_EQ(0, Access("/dev/null", R_OK)); 199 EXPECT_EQ(0, Access("/dev/null", W_OK)); 200 EXPECT_EQ(0, Access("/dev/null", R_OK | W_OK)); 201 EXPECT_EQ(-EACCES, Access("/dev/null", X_OK)); 202 EXPECT_EQ(-EACCES, Access("/dev/null", R_OK | X_OK)); 203 204 EXPECT_EQ(0, Access("/dev/zero", R_OK)); 205 EXPECT_EQ(-EACCES, Access("/dev/zero", W_OK)); 206 EXPECT_EQ(-EACCES, Access("/dev/zero", R_OK | W_OK)); 207 208 EXPECT_EQ(-ENOENT, Access("/var/empty/qwertyuiop", R_OK)); 209 EXPECT_EQ(-EACCES, Access("/var/empty/qwertyuiop", W_OK)); 210 211 EXPECT_EQ(0, Access("/proc/self", F_OK)); 212 EXPECT_EQ(-EACCES, Access("/proc/self", R_OK)); 213 214 EXPECT_EQ(-EACCES, Access("/proc/self/stat", F_OK)); 215 216 EXPECT_EQ(0, Access("/tmp", X_OK)); 217 EXPECT_EQ(0, Access("/tmp", R_OK | X_OK)); 218 EXPECT_EQ(0, Access("/tmp", R_OK | W_OK | X_OK)); 219 EXPECT_EQ(0, Access("/proc/self", X_OK)); 220 221 EXPECT_EQ(0, Access("/etc", R_OK | X_OK)); 222 EXPECT_EQ(-EACCES, Access("/etc", W_OK)); 223 } 224 225 TEST_F(SandboxBrokerTest, Stat) { 226 statstruct realStat, brokeredStat; 227 ASSERT_EQ(0, statsyscall("/dev/null", &realStat)) << "Shouldn't ever fail!"; 228 EXPECT_EQ(0, Stat("/dev/null", &brokeredStat)); 229 EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino); 230 EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev); 231 232 #if defined(__clang__) || defined(__GNUC__) 233 # pragma GCC diagnostic push 234 # pragma GCC diagnostic ignored "-Wnonnull" 235 #endif 236 EXPECT_EQ(-1, statsyscall(nullptr, &realStat)); 237 EXPECT_EQ(errno, EFAULT); 238 239 EXPECT_EQ(-EFAULT, Stat(nullptr, &brokeredStat)); 240 #if defined(__clang__) || defined(__GNUC__) 241 # pragma GCC diagnostic pop 242 #endif 243 244 EXPECT_EQ(-ENOENT, Stat("/var/empty/qwertyuiop", &brokeredStat)); 245 EXPECT_EQ(-EACCES, Stat("/dev", &brokeredStat)); 246 247 EXPECT_EQ(0, Stat("/proc/self", &brokeredStat)); 248 EXPECT_TRUE(S_ISDIR(brokeredStat.st_mode)); 249 } 250 251 TEST_F(SandboxBrokerTest, LStat) { 252 statstruct realStat, brokeredStat; 253 ASSERT_EQ(0, lstatsyscall("/dev/null", &realStat)); 254 EXPECT_EQ(0, LStat("/dev/null", &brokeredStat)); 255 EXPECT_EQ(realStat.st_ino, brokeredStat.st_ino); 256 EXPECT_EQ(realStat.st_rdev, brokeredStat.st_rdev); 257 258 EXPECT_EQ(-ENOENT, LStat("/var/empty/qwertyuiop", &brokeredStat)); 259 EXPECT_EQ(-EACCES, LStat("/dev", &brokeredStat)); 260 261 EXPECT_EQ(0, LStat("/proc/self", &brokeredStat)); 262 EXPECT_TRUE(S_ISLNK(brokeredStat.st_mode)); 263 } 264 265 static void PrePostTestCleanup(void) { 266 unlink("/tmp/blublu"); 267 rmdir("/tmp/blublu"); 268 unlink("/tmp/nope"); 269 rmdir("/tmp/nope"); 270 unlink("/tmp/blublublu"); 271 rmdir("/tmp/blublublu"); 272 } 273 274 TEST_F(SandboxBrokerTest, Chmod) { 275 PrePostTestCleanup(); 276 277 int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); 278 ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; 279 close(fd); 280 // Set read only. SandboxBroker enforces 0600 mode flags. 281 ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR)); 282 EXPECT_EQ(-EACCES, Access("/tmp/blublu", W_OK)); 283 statstruct realStat; 284 EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat)); 285 EXPECT_EQ((mode_t)S_IRUSR, realStat.st_mode & 0777); 286 287 ASSERT_EQ(0, Chmod("/tmp/blublu", S_IRUSR | S_IWUSR)); 288 EXPECT_EQ(0, statsyscall("/tmp/blublu", &realStat)); 289 EXPECT_EQ((mode_t)(S_IRUSR | S_IWUSR), realStat.st_mode & 0777); 290 EXPECT_EQ(0, unlink("/tmp/blublu")); 291 292 PrePostTestCleanup(); 293 } 294 295 TEST_F(SandboxBrokerTest, Link) { 296 PrePostTestCleanup(); 297 298 int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); 299 ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; 300 close(fd); 301 ASSERT_EQ(0, Link("/tmp/blublu", "/tmp/blublublu")); 302 EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); 303 // Not whitelisted target path 304 EXPECT_EQ(-EACCES, Link("/tmp/blublu", "/tmp/nope")); 305 EXPECT_EQ(0, unlink("/tmp/blublublu")); 306 EXPECT_EQ(0, unlink("/tmp/blublu")); 307 308 PrePostTestCleanup(); 309 } 310 311 TEST_F(SandboxBrokerTest, Symlink) { 312 PrePostTestCleanup(); 313 314 int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); 315 ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; 316 close(fd); 317 ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu")); 318 EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); 319 statstruct aStat; 320 ASSERT_EQ(0, lstatsyscall("/tmp/blublublu", &aStat)); 321 EXPECT_EQ((mode_t)S_IFLNK, aStat.st_mode & S_IFMT); 322 // Not whitelisted target path 323 EXPECT_EQ(-EACCES, Symlink("/tmp/blublu", "/tmp/nope")); 324 EXPECT_EQ(0, unlink("/tmp/blublublu")); 325 EXPECT_EQ(0, unlink("/tmp/blublu")); 326 327 PrePostTestCleanup(); 328 } 329 330 TEST_F(SandboxBrokerTest, Mkdir) { 331 PrePostTestCleanup(); 332 333 ASSERT_EQ(0, mkdir("/tmp/blublu", 0600)) 334 << "Creating dir /tmp/blublu failed."; 335 EXPECT_EQ(0, Access("/tmp/blublu", F_OK)); 336 // Not whitelisted target path 337 EXPECT_EQ(-EACCES, Mkdir("/tmp/nope", 0600)) 338 << "Creating dir without MAY_CREATE succeed."; 339 EXPECT_EQ(0, rmdir("/tmp/blublu")); 340 EXPECT_EQ(-EEXIST, Mkdir("/proc/self", 0600)) 341 << "Creating uncreatable dir that already exists didn't fail correctly."; 342 EXPECT_EQ(-EEXIST, Mkdir("/dev/zero", 0600)) 343 << "Creating uncreatable dir over preexisting file didn't fail " 344 "correctly."; 345 346 PrePostTestCleanup(); 347 } 348 349 TEST_F(SandboxBrokerTest, Rename) { 350 PrePostTestCleanup(); 351 352 ASSERT_EQ(0, mkdir("/tmp/blublu", 0600)) 353 << "Creating dir /tmp/blublu failed."; 354 EXPECT_EQ(0, Access("/tmp/blublu", F_OK)); 355 ASSERT_EQ(0, Rename("/tmp/blublu", "/tmp/blublublu")); 356 EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); 357 EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK)); 358 // Not whitelisted target path 359 EXPECT_EQ(-EACCES, Rename("/tmp/blublublu", "/tmp/nope")) 360 << "Renaming dir without write access succeed."; 361 EXPECT_EQ(0, rmdir("/tmp/blublublu")); 362 363 PrePostTestCleanup(); 364 } 365 366 TEST_F(SandboxBrokerTest, Rmdir) { 367 PrePostTestCleanup(); 368 369 ASSERT_EQ(0, mkdir("/tmp/blublu", 0600)) 370 << "Creating dir /tmp/blublu failed."; 371 EXPECT_EQ(0, Access("/tmp/blublu", F_OK)); 372 ASSERT_EQ(0, Rmdir("/tmp/blublu")); 373 EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK)); 374 // Bypass sandbox to create a non-deletable dir 375 ASSERT_EQ(0, mkdir("/tmp/nope", 0600)); 376 EXPECT_EQ(-EACCES, Rmdir("/tmp/nope")); 377 378 PrePostTestCleanup(); 379 } 380 381 TEST_F(SandboxBrokerTest, Unlink) { 382 PrePostTestCleanup(); 383 384 int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); 385 ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; 386 close(fd); 387 EXPECT_EQ(0, Access("/tmp/blublu", F_OK)); 388 EXPECT_EQ(0, Unlink("/tmp/blublu")); 389 EXPECT_EQ(-ENOENT, Access("/tmp/blublu", F_OK)); 390 // Bypass sandbox to write a non-deletable file 391 fd = open("/tmp/nope", O_WRONLY | O_CREAT, 0600); 392 ASSERT_GE(fd, 0) << "Opening /tmp/nope for writing failed."; 393 close(fd); 394 EXPECT_EQ(-EACCES, Unlink("/tmp/nope")); 395 396 PrePostTestCleanup(); 397 } 398 399 TEST_F(SandboxBrokerTest, Readlink) { 400 PrePostTestCleanup(); 401 402 int fd = Open("/tmp/blublu", O_WRONLY | O_CREAT); 403 ASSERT_GE(fd, 0) << "Opening /tmp/blublu for writing failed."; 404 close(fd); 405 ASSERT_EQ(0, Symlink("/tmp/blublu", "/tmp/blublublu")); 406 EXPECT_EQ(0, Access("/tmp/blublublu", F_OK)); 407 char linkBuff[256]; 408 EXPECT_EQ(11, Readlink("/tmp/blublublu", linkBuff, sizeof(linkBuff))); 409 linkBuff[11] = '\0'; 410 EXPECT_EQ(0, strcmp(linkBuff, "/tmp/blublu")); 411 412 PrePostTestCleanup(); 413 } 414 415 TEST_F(SandboxBrokerTest, MultiThreadOpen) { 416 RunOnManyThreads<SandboxBrokerTest, 417 &SandboxBrokerTest::MultiThreadOpenWorker>(); 418 } 419 void SandboxBrokerTest::MultiThreadOpenWorker() { 420 static const int kNumLoops = 10000; 421 422 for (int i = 1; i <= kNumLoops; ++i) { 423 int nullfd = Open("/dev/null", O_RDONLY); 424 int zerofd = Open("/dev/zero", O_RDONLY); 425 ASSERT_GE(nullfd, 0) << "Loop " << i << "/" << kNumLoops; 426 ASSERT_GE(zerofd, 0) << "Loop " << i << "/" << kNumLoops; 427 char c; 428 ASSERT_EQ(0, read(nullfd, &c, 1)) << "Loop " << i << "/" << kNumLoops; 429 ASSERT_EQ(1, read(zerofd, &c, 1)) << "Loop " << i << "/" << kNumLoops; 430 ASSERT_EQ('\0', c) << "Loop " << i << "/" << kNumLoops; 431 close(nullfd); 432 close(zerofd); 433 } 434 } 435 436 TEST_F(SandboxBrokerTest, MultiThreadStat) { 437 RunOnManyThreads<SandboxBrokerTest, 438 &SandboxBrokerTest::MultiThreadStatWorker>(); 439 } 440 void SandboxBrokerTest::MultiThreadStatWorker() { 441 static const int kNumLoops = 7500; 442 statstruct nullStat, zeroStat, selfStat; 443 dev_t realNullDev, realZeroDev; 444 ino_t realSelfInode; 445 446 ASSERT_EQ(0, statsyscall("/dev/null", &nullStat)) << "Shouldn't ever fail!"; 447 ASSERT_EQ(0, statsyscall("/dev/zero", &zeroStat)) << "Shouldn't ever fail!"; 448 ASSERT_EQ(0, lstatsyscall("/proc/self", &selfStat)) << "Shouldn't ever fail!"; 449 ASSERT_TRUE(S_ISLNK(selfStat.st_mode)) 450 << "Shouldn't ever fail!"; 451 realNullDev = nullStat.st_rdev; 452 realZeroDev = zeroStat.st_rdev; 453 realSelfInode = selfStat.st_ino; 454 for (int i = 1; i <= kNumLoops; ++i) { 455 ASSERT_EQ(0, Stat("/dev/null", &nullStat)) 456 << "Loop " << i << "/" << kNumLoops; 457 ASSERT_EQ(0, Stat("/dev/zero", &zeroStat)) 458 << "Loop " << i << "/" << kNumLoops; 459 ASSERT_EQ(0, LStat("/proc/self", &selfStat)) 460 << "Loop " << i << "/" << kNumLoops; 461 462 ASSERT_EQ(realNullDev, nullStat.st_rdev) 463 << "Loop " << i << "/" << kNumLoops; 464 ASSERT_EQ(realZeroDev, zeroStat.st_rdev) 465 << "Loop " << i << "/" << kNumLoops; 466 ASSERT_TRUE(S_ISLNK(selfStat.st_mode)) 467 << "Loop " << i << "/" << kNumLoops; 468 ASSERT_EQ(realSelfInode, selfStat.st_ino) 469 << "Loop " << i << "/" << kNumLoops; 470 } 471 } 472 473 #if 0 474 class SandboxBrokerSigStress : public SandboxBrokerTest 475 { 476 int mSigNum; 477 struct sigaction mOldAction; 478 Atomic<void*> mVoidPtr; 479 480 static void SigHandler(int aSigNum, siginfo_t* aSigInfo, void *aCtx) { 481 ASSERT_EQ(SI_QUEUE, aSigInfo->si_code); 482 SandboxBrokerSigStress* that = 483 static_cast<SandboxBrokerSigStress*>(aSigInfo->si_value.sival_ptr); 484 ASSERT_EQ(that->mSigNum, aSigNum); 485 that->DoSomething(); 486 } 487 488 protected: 489 Atomic<int> mTestIter; 490 sem_t mSemaphore; 491 492 void SignalThread(pthread_t aThread) { 493 union sigval sv; 494 sv.sival_ptr = this; 495 ASSERT_NE(0, mSigNum); 496 ASSERT_EQ(0, pthread_sigqueue(aThread, mSigNum, sv)); 497 } 498 499 virtual void SetUp() { 500 ASSERT_EQ(0, sem_init(&mSemaphore, 0, 0)); 501 mVoidPtr = nullptr; 502 mSigNum = 0; 503 for (int sigNum = SIGRTMIN; sigNum < SIGRTMAX; ++sigNum) { 504 ASSERT_EQ(0, sigaction(sigNum, nullptr, &mOldAction)); 505 if ((mOldAction.sa_flags & SA_SIGINFO) == 0 && 506 mOldAction.sa_handler == SIG_DFL) { 507 struct sigaction newAction; 508 PodZero(&newAction); 509 newAction.sa_flags = SA_SIGINFO; 510 newAction.sa_sigaction = SigHandler; 511 ASSERT_EQ(0, sigaction(sigNum, &newAction, nullptr)); 512 mSigNum = sigNum; 513 break; 514 } 515 } 516 ASSERT_NE(mSigNum, 0); 517 518 SandboxBrokerTest::SetUp(); 519 } 520 521 virtual void TearDown() { 522 ASSERT_EQ(0, sem_destroy(&mSemaphore)); 523 if (mSigNum != 0) { 524 ASSERT_EQ(0, sigaction(mSigNum, &mOldAction, nullptr)); 525 } 526 if (mVoidPtr) { 527 free(mVoidPtr); 528 } 529 } 530 531 void DoSomething(); 532 533 public: 534 void MallocWorker(); 535 void FreeWorker(); 536 }; 537 538 TEST_F(SandboxBrokerSigStress, StressTest) 539 { 540 static const int kIters = 6250; 541 static const int kNsecPerIterPerIter = 4; 542 struct timespec delay = { 0, 0 }; 543 pthread_t threads[2]; 544 545 mTestIter = kIters; 546 547 StartThread<SandboxBrokerSigStress, 548 &SandboxBrokerSigStress::MallocWorker>(&threads[0]); 549 StartThread<SandboxBrokerSigStress, 550 &SandboxBrokerSigStress::FreeWorker>(&threads[1]); 551 552 for (int i = kIters; i > 0; --i) { 553 SignalThread(threads[i % 2]); 554 while (sem_wait(&mSemaphore) == -1 && errno == EINTR) 555 /* retry */; 556 ASSERT_EQ(i - 1, mTestIter); 557 delay.tv_nsec += kNsecPerIterPerIter; 558 struct timespec req = delay, rem; 559 while (nanosleep(&req, &rem) == -1 && errno == EINTR) { 560 req = rem; 561 } 562 } 563 void *retval; 564 ASSERT_EQ(0, pthread_join(threads[0], &retval)); 565 ASSERT_EQ(nullptr, retval); 566 ASSERT_EQ(0, pthread_join(threads[1], &retval)); 567 ASSERT_EQ(nullptr, retval); 568 } 569 570 void 571 SandboxBrokerSigStress::MallocWorker() 572 { 573 static const size_t kSize = 64; 574 575 void* mem = malloc(kSize); 576 while (mTestIter > 0) { 577 ASSERT_NE(mem, mVoidPtr); 578 mem = mVoidPtr.exchange(mem); 579 if (mem) { 580 sched_yield(); 581 } else { 582 mem = malloc(kSize); 583 } 584 } 585 if (mem) { 586 free(mem); 587 } 588 } 589 590 void 591 SandboxBrokerSigStress::FreeWorker() 592 { 593 void *mem = nullptr; 594 while (mTestIter > 0) { 595 mem = mVoidPtr.exchange(mem); 596 if (mem) { 597 free(mem); 598 mem = nullptr; 599 } else { 600 sched_yield(); 601 } 602 } 603 } 604 605 void 606 SandboxBrokerSigStress::DoSomething() 607 { 608 int fd; 609 char c; 610 struct stat st; 611 612 //fprintf(stderr, "Don't try this at home: %d\n", static_cast<int>(mTestIter)); 613 switch (mTestIter % 5) { 614 case 0: 615 fd = Open("/dev/null", O_RDWR); 616 ASSERT_GE(fd, 0); 617 ASSERT_EQ(0, read(fd, &c, 1)); 618 close(fd); 619 break; 620 case 1: 621 fd = Open("/dev/zero", O_RDONLY); 622 ASSERT_GE(fd, 0); 623 ASSERT_EQ(1, read(fd, &c, 1)); 624 ASSERT_EQ('\0', c); 625 close(fd); 626 break; 627 case 2: 628 ASSERT_EQ(0, Access("/dev/null", W_OK)); 629 break; 630 case 3: 631 ASSERT_EQ(0, Stat("/proc/self", &st)); 632 ASSERT_TRUE(S_ISDIR(st.st_mode)); 633 break; 634 case 4: 635 ASSERT_EQ(0, LStat("/proc/self", &st)); 636 ASSERT_TRUE(S_ISLNK(st.st_mode)); 637 break; 638 } 639 mTestIter--; 640 sem_post(&mSemaphore); 641 } 642 #endif 643 644 // Check for fd leaks when creating/destroying a broker instance (bug 645 // 1719391). 646 // 647 // (This uses a different test group because it doesn't use the 648 // fixture class, and gtest doesn't allow mixing TEST and TEST_F in 649 // the same group.) 650 TEST(SandboxBrokerMisc, FileDescLeak) 651 { 652 // If this value is increased in the future, check that it won't 653 // cause the test to take an excessive amount of time. 654 // (Alternately, this test could be changed to run a smaller number 655 // of cycles and check for increased fd usage afterwards; some care 656 // would be needed to avoid false positives.) 657 static constexpr size_t kCycles = 4096; 658 struct rlimit oldLimit; 659 bool changedLimit = false; 660 661 // At the time of this writing, we raise the fd soft limit to 4096 662 // (or to the hard limit, if less than that), but we don't lower it 663 // if it was higher than 4096. To allow for that case, or if 664 // Gecko's preferred limit changes, the limit is reduced while this 665 // test is running: 666 ASSERT_EQ(getrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno); 667 if (oldLimit.rlim_cur == RLIM_INFINITY || 668 oldLimit.rlim_cur > static_cast<rlim_t>(kCycles)) { 669 struct rlimit newLimit = oldLimit; 670 newLimit.rlim_cur = static_cast<rlim_t>(kCycles); 671 ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &newLimit), 0) << strerror(errno); 672 changedLimit = true; 673 } 674 675 pid_t pid = getpid(); 676 for (size_t i = 0; i < kCycles; ++i) { 677 auto policy = MakeUnique<SandboxBroker::Policy>(); 678 // Currently nothing in `Create` tries to check for or 679 // special-case an empty policy, but just in case: 680 policy->AddPath(SandboxBroker::MAY_READ, "/dev/null", 681 SandboxBroker::Policy::AddAlways); 682 ipc::FileDescriptor fd; 683 RefPtr<SandboxBroker> broker = 684 SandboxBroker::Create(std::move(policy), pid, fd); 685 ASSERT_TRUE(broker != nullptr); 686 ASSERT_TRUE(fd.IsValid()); 687 broker->Terminate(); 688 } 689 690 if (changedLimit) { 691 ASSERT_EQ(setrlimit(RLIMIT_NOFILE, &oldLimit), 0) << strerror(errno); 692 } 693 } 694 695 // Bug 1958444: In theory this test could fail intermittently: it 696 // creates a large number of threads which will do a small amount of 697 // work and then exit, but intentionally doesn't join them, so there 698 // could be a large number of threads actually live at once, even if 699 // they would be cleaned up correctly when they exit. 700 // 701 // To test locally, delete the `DISABLED_` prefix and rebuild. 702 TEST(SandboxBrokerMisc, DISABLED_StackLeak) 703 { 704 // The idea is that kCycles * DEFAULT_STACK_SIZE is more than 4GiB 705 // so this fails on 32-bit if the thread stack is leaked. This 706 // isn't ideal; it would be better to either use resource limits 707 // (maybe RLIMIT_AS) or measure address space usage (getrusage or 708 // procfs), to detect such bugs with a smaller amount of leakage. 709 static constexpr size_t kCycles = 16384; 710 711 if (nsIThreadManager::DEFAULT_STACK_SIZE) { 712 EXPECT_GE(nsIThreadManager::DEFAULT_STACK_SIZE, size_t(256 * 1024)); 713 } 714 715 pid_t pid = getpid(); 716 for (size_t i = 0; i < kCycles; ++i) { 717 auto policy = MakeUnique<SandboxBroker::Policy>(); 718 // Currently nothing in `Create` tries to check for or 719 // special-case an empty policy, but just in case: 720 policy->AddPath(SandboxBroker::MAY_READ, "/dev/null", 721 SandboxBroker::Policy::AddAlways); 722 ipc::FileDescriptor fd; 723 RefPtr<SandboxBroker> broker = 724 SandboxBroker::Create(std::move(policy), pid, fd); 725 ASSERT_TRUE(broker != nullptr) 726 << "iter " << i; 727 ASSERT_TRUE(fd.IsValid()) 728 << "iter " << i; 729 broker = nullptr; 730 } 731 } 732 733 } // namespace mozilla