SandboxBroker.cpp (35822B)
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 "SandboxBroker.h" 8 #include "SandboxInfo.h" 9 10 #include "SandboxProfilerParent.h" 11 #include "SandboxLogging.h" 12 13 #include "SandboxBrokerUtils.h" 14 15 #include <dirent.h> 16 #include <errno.h> 17 #include <fcntl.h> 18 #include <sys/socket.h> 19 #include <sys/stat.h> 20 #include <sys/types.h> 21 #include <sys/un.h> 22 #include <unistd.h> 23 24 #ifdef XP_LINUX 25 # include <sys/prctl.h> 26 #endif 27 28 #include <utility> 29 30 #include "GeckoProfiler.h" 31 #include "SpecialSystemDirectory.h" 32 #include "base/string_util.h" 33 #include "mozilla/Assertions.h" 34 #include "mozilla/Atomics.h" 35 #include "mozilla/Sprintf.h" 36 #include "mozilla/ipc/FileDescriptor.h" 37 #include "nsAppDirectoryServiceDefs.h" 38 #include "nsDirectoryServiceDefs.h" 39 #include "nsThreadUtils.h" 40 #include "sandbox/linux/system_headers/linux_syscalls.h" 41 42 namespace mozilla { 43 44 namespace { 45 46 // kernel level limit defined at 47 // https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L301 48 // used at 49 // https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L1087 50 static const int kThreadNameMaxSize = 16; 51 52 static Atomic<size_t> gNumBrokers; 53 54 } // namespace 55 56 // This constructor signals failure by setting mFileDesc and aClientFd to -1. 57 SandboxBroker::SandboxBroker(UniquePtr<const Policy> aPolicy, int aChildPid, 58 int& aClientFd) 59 : mChildPid(aChildPid), mPolicy(std::move(aPolicy)) { 60 ++gNumBrokers; 61 int fds[2]; 62 if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, fds)) { 63 SANDBOX_LOG_ERRNO("SandboxBroker: socketpair failed (%d brokers)", 64 gNumBrokers.operator size_t()); 65 mFileDesc = -1; 66 aClientFd = -1; 67 return; 68 } 69 mFileDesc = fds[0]; 70 aClientFd = fds[1]; 71 72 // When the thread will start it may already be too late to correctly care 73 // about reference handling. Make sure the increment happens before the thread 74 // is created to avoid races. Details in SandboxBroker::ThreadMain(). 75 NS_ADDREF_THIS(); 76 77 if (!PlatformThread::Create(0, this, &mThread)) { 78 SANDBOX_LOG_ERRNO("SandboxBroker: thread creation failed (%d brokers)", 79 gNumBrokers.operator size_t()); 80 close(mFileDesc); 81 close(aClientFd); 82 mFileDesc = -1; 83 aClientFd = -1; 84 } 85 } 86 87 already_AddRefed<SandboxBroker> SandboxBroker::Create( 88 UniquePtr<const Policy> aPolicy, int aChildPid, 89 ipc::FileDescriptor& aClientFdOut) { 90 int clientFd; 91 // Can't use MakeRefPtr here because the constructor is private. 92 RefPtr<SandboxBroker> rv( 93 new SandboxBroker(std::move(aPolicy), aChildPid, clientFd)); 94 if (clientFd < 0) { 95 rv = nullptr; 96 } else { 97 // FileDescriptor can be constructed from an int, but that dup()s 98 // the fd; instead, transfer ownership: 99 aClientFdOut = ipc::FileDescriptor(UniqueFileHandle(clientFd)); 100 } 101 return rv.forget(); 102 } 103 104 void SandboxBroker::Terminate() { 105 // If the constructor failed, there's nothing to be done here. 106 if (mFileDesc < 0) { 107 return; 108 } 109 110 // Join() on the same thread while working with errno EDEADLK is technically 111 // not POSIX compliant: 112 // https://pubs.opengroup.org/onlinepubs/9799919799/functions/pthread_join.html#:~:text=refers%20to%20the%20calling%20thread 113 if (mThread != pthread_self()) { 114 shutdown(mFileDesc, SHUT_RD); 115 // The thread will now get EOF even if the client hasn't exited. 116 PlatformThread::Join(mThread); 117 } else { 118 // Nothing is waiting for this thread, so detach it to avoid 119 // memory leaks. 120 int rv = pthread_detach(pthread_self()); 121 MOZ_ALWAYS_TRUE(rv == 0); 122 } 123 124 // Now that the thread has exited, the fd will no longer be accessed. 125 close(mFileDesc); 126 // Having ensured that this object outlives the thread, this 127 // destructor can now return. 128 129 mFileDesc = -1; 130 } 131 132 SandboxBroker::~SandboxBroker() { 133 Terminate(); 134 --gNumBrokers; 135 } 136 137 SandboxBroker::Policy::Policy() = default; 138 SandboxBroker::Policy::~Policy() = default; 139 140 SandboxBroker::Policy::Policy(const Policy& aOther) 141 : mMap(aOther.mMap.Clone()) {} 142 143 // Chromium 144 // sandbox/linux/syscall_broker/broker_file_permission.cc 145 // Async signal safe 146 bool SandboxBroker::Policy::ValidatePath(const char* path) const { 147 if (!path) return false; 148 149 const size_t len = strlen(path); 150 // No empty paths 151 if (len == 0) return false; 152 // Paths must be absolute and not relative 153 if (path[0] != '/') return false; 154 // No trailing / (but "/" is valid) 155 if (len > 1 && path[len - 1] == '/') return false; 156 // No trailing /. 157 if (len >= 2 && path[len - 2] == '/' && path[len - 1] == '.') return false; 158 // No trailing /.. 159 if (len >= 3 && path[len - 3] == '/' && path[len - 2] == '.' && 160 path[len - 1] == '.') 161 return false; 162 // No /../ anywhere 163 for (size_t i = 0; i < len; i++) { 164 if (path[i] == '/' && (len - i) > 3) { 165 if (path[i + 1] == '.' && path[i + 2] == '.' && path[i + 3] == '/') { 166 return false; 167 } 168 } 169 } 170 return true; 171 } 172 173 void SandboxBroker::Policy::AddPath(int aPerms, const char* aPath, 174 AddCondition aCond) { 175 nsDependentCString path(aPath); 176 MOZ_ASSERT(path.Length() <= kMaxPathLen); 177 if (aCond == AddIfExistsNow) { 178 struct stat statBuf; 179 if (lstat(aPath, &statBuf) != 0) { 180 return; 181 } 182 } 183 auto& perms = mMap.LookupOrInsert(path, MAY_ACCESS); 184 MOZ_ASSERT(perms & MAY_ACCESS); 185 186 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 187 SANDBOX_LOG("policy for %s: %d -> %d", aPath, perms, perms | aPerms); 188 } 189 perms |= aPerms; 190 } 191 192 void SandboxBroker::Policy::AddTree(int aPerms, const char* aPath) { 193 struct stat statBuf; 194 195 if (stat(aPath, &statBuf) != 0) { 196 return; 197 } 198 199 if (!S_ISDIR(statBuf.st_mode)) { 200 return; 201 } 202 203 Policy::AddTreeInternal(aPerms, aPath); 204 } 205 206 void SandboxBroker::Policy::AddFutureDir(int aPerms, const char* aPath) { 207 Policy::AddTreeInternal(aPerms, aPath); 208 } 209 210 void SandboxBroker::Policy::AddTreeInternal(int aPerms, const char* aPath) { 211 // Add a Prefix permission on things inside the dir. 212 nsDependentCString path(aPath); 213 MOZ_ASSERT(path.Length() <= kMaxPathLen - 1); 214 // Enforce trailing / on aPath 215 if (path.Last() != '/') { 216 path.Append('/'); 217 } 218 Policy::AddPrefixInternal(aPerms, path); 219 220 // Add a path permission on the dir itself so it can 221 // be opened. We're guaranteed to have a trailing / now, 222 // so just cut that. 223 path.Truncate(path.Length() - 1); 224 if (!path.IsEmpty()) { 225 Policy::AddPath(aPerms, path.get(), AddAlways); 226 } 227 } 228 229 void SandboxBroker::Policy::AddPrefix(int aPerms, const char* aPath) { 230 Policy::AddPrefixInternal(aPerms, nsDependentCString(aPath)); 231 } 232 233 void SandboxBroker::Policy::AddPrefixInternal(int aPerms, 234 const nsACString& aPath) { 235 auto& perms = mMap.LookupOrInsert(aPath, MAY_ACCESS); 236 MOZ_ASSERT(perms & MAY_ACCESS); 237 238 int newPerms = perms | aPerms | RECURSIVE; 239 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 240 SANDBOX_LOG("policy for %s: %d -> %d", PromiseFlatCString(aPath).get(), 241 perms, newPerms); 242 } 243 perms = newPerms; 244 } 245 246 void SandboxBroker::Policy::AddFilePrefix(int aPerms, const char* aDir, 247 const char* aPrefix) { 248 size_t prefixLen = strlen(aPrefix); 249 DIR* dirp = opendir(aDir); 250 struct dirent* de; 251 if (!dirp) { 252 return; 253 } 254 while ((de = readdir(dirp))) { 255 if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0 && 256 strncmp(de->d_name, aPrefix, prefixLen) == 0) { 257 nsAutoCString subPath; 258 subPath.Assign(aDir); 259 subPath.Append('/'); 260 subPath.Append(de->d_name); 261 AddPath(aPerms, subPath.get(), AddAlways); 262 } 263 } 264 closedir(dirp); 265 } 266 267 void SandboxBroker::Policy::AddDynamic(int aPerms, const char* aPath) { 268 struct stat statBuf; 269 bool exists = (stat(aPath, &statBuf) == 0); 270 271 if (!exists) { 272 AddPrefix(aPerms, aPath); 273 } else { 274 size_t len = strlen(aPath); 275 if (!len) return; 276 if (aPath[len - 1] == '/') { 277 AddTree(aPerms, aPath); 278 } else { 279 AddPath(aPerms, aPath); 280 } 281 } 282 } 283 284 void SandboxBroker::Policy::AddAncestors(const char* aPath, int aPerms) { 285 nsAutoCString path(aPath); 286 287 while (true) { 288 const auto lastSlash = path.RFindCharInSet("/"); 289 if (lastSlash <= 0) { 290 MOZ_ASSERT(lastSlash == 0); 291 return; 292 } 293 path.Truncate(lastSlash); 294 AddPath(aPerms, path.get()); 295 } 296 } 297 298 void SandboxBroker::Policy::FixRecursivePermissions() { 299 // This builds an entirely new hashtable in order to avoid iterator 300 // invalidation problems. 301 PathPermissionMap oldMap; 302 mMap.SwapElements(oldMap); 303 304 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 305 SANDBOX_LOG("fixing recursive policy entries"); 306 } 307 308 for (const auto& entry : oldMap) { 309 const nsACString& path = entry.GetKey(); 310 const int& localPerms = entry.GetData(); 311 int inheritedPerms = 0; 312 313 nsAutoCString ancestor(path); 314 // This is slightly different from the loop in AddAncestors: it 315 // leaves the trailing slashes attached so they'll match AddTree 316 // entries. 317 while (true) { 318 // Last() release-asserts that the string is not empty. We 319 // should never have empty keys in the map, and the Truncate() 320 // below will always give us a non-empty string. 321 if (ancestor.Last() == '/') { 322 ancestor.Truncate(ancestor.Length() - 1); 323 } 324 const auto lastSlash = ancestor.RFindCharInSet("/"); 325 if (lastSlash < 0) { 326 MOZ_ASSERT(ancestor.IsEmpty()); 327 break; 328 } 329 ancestor.Truncate(lastSlash + 1); 330 const int ancestorPerms = oldMap.Get(ancestor); 331 if (ancestorPerms & RECURSIVE) { 332 // if a child is set with FORCE_DENY, do not compute inheritedPerms 333 if ((localPerms & FORCE_DENY) == FORCE_DENY) { 334 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 335 SANDBOX_LOG("skip inheritence policy for %s: %d", 336 PromiseFlatCString(path).get(), localPerms); 337 } 338 } else { 339 inheritedPerms |= ancestorPerms & ~RECURSIVE; 340 } 341 } 342 } 343 344 const int newPerms = localPerms | inheritedPerms; 345 if ((newPerms & ~RECURSIVE) == inheritedPerms) { 346 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 347 SANDBOX_LOG("removing redundant %s: %d -> %d", 348 PromiseFlatCString(path).get(), localPerms, newPerms); 349 } 350 // Skip adding this entry to the new map. 351 continue; 352 } 353 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 354 SANDBOX_LOG("new policy for %s: %d -> %d", PromiseFlatCString(path).get(), 355 localPerms, newPerms); 356 } 357 mMap.InsertOrUpdate(path, newPerms); 358 } 359 } 360 361 int SandboxBroker::Policy::Lookup(const nsACString& aPath) const { 362 // Early exit for paths explicitly found in the 363 // whitelist. 364 // This means they will not gain extra permissions 365 // from recursive paths. 366 int perms = mMap.Get(aPath); 367 if (perms) { 368 return perms; 369 } 370 371 // Not a legally constructed path 372 if (!ValidatePath(PromiseFlatCString(aPath).get())) return 0; 373 374 // Now it's either an illegal access, or a recursive 375 // directory permission. We'll have to check the entire 376 // whitelist for the best match (slower). 377 int allPerms = 0; 378 for (const auto& entry : mMap) { 379 const nsACString& whiteListPath = entry.GetKey(); 380 const int& perms = entry.GetData(); 381 382 if (!(perms & RECURSIVE)) continue; 383 384 // passed part starts with something on the whitelist 385 if (StringBeginsWith(aPath, whiteListPath)) { 386 allPerms |= perms; 387 } 388 } 389 390 // Strip away the RECURSIVE flag as it doesn't 391 // necessarily apply to aPath. 392 return allPerms & ~RECURSIVE; 393 } 394 395 static bool AllowOperation(int aReqFlags, int aPerms) { 396 int needed = 0; 397 if (aReqFlags & R_OK) { 398 needed |= SandboxBroker::MAY_READ; 399 } 400 if (aReqFlags & W_OK) { 401 needed |= SandboxBroker::MAY_WRITE; 402 } 403 // We don't really allow executing anything, 404 // so in true unix tradition we hijack this 405 // for directory access (creation). 406 if (aReqFlags & X_OK) { 407 needed |= SandboxBroker::MAY_CREATE; 408 } 409 return (aPerms & needed) == needed; 410 } 411 412 static bool AllowAccess(int aReqFlags, int aPerms) { 413 if (aReqFlags & ~(R_OK | W_OK | X_OK | F_OK)) { 414 return false; 415 } 416 int needed = 0; 417 if (aReqFlags & R_OK) { 418 needed |= SandboxBroker::MAY_READ; 419 } 420 if (aReqFlags & W_OK) { 421 needed |= SandboxBroker::MAY_WRITE; 422 } 423 return (aPerms & needed) == needed; 424 } 425 426 // These flags are added to all opens to prevent possible side-effects 427 // on this process. These shouldn't be relevant to the child process 428 // in any case due to the sandboxing restrictions on it. (See also 429 // the use of MSG_CMSG_CLOEXEC in SandboxBrokerCommon.cpp). 430 static const int kRequiredOpenFlags = O_CLOEXEC | O_NOCTTY; 431 432 // Linux originally assigned a flag bit to O_SYNC but implemented the 433 // semantics standardized as O_DSYNC; later, that bit was renamed and 434 // a new bit was assigned to the full O_SYNC, and O_SYNC was redefined 435 // to be both bits. As a result, this #define is needed to compensate 436 // for outdated kernel headers like Android's. 437 #define O_SYNC_NEW 04010000 438 static const int kAllowedOpenFlags = 439 O_APPEND | O_DIRECT | O_DIRECTORY | O_EXCL | O_LARGEFILE | O_NOATIME | 440 O_NOCTTY | O_NOFOLLOW | O_NONBLOCK | O_NDELAY | O_SYNC_NEW | O_TRUNC | 441 O_CLOEXEC | O_CREAT; 442 #undef O_SYNC_NEW 443 444 static bool AllowOpen(int aReqFlags, int aPerms) { 445 if (aReqFlags & ~O_ACCMODE & ~kAllowedOpenFlags) { 446 return false; 447 } 448 int needed; 449 switch (aReqFlags & O_ACCMODE) { 450 case O_RDONLY: 451 needed = SandboxBroker::MAY_READ; 452 break; 453 case O_WRONLY: 454 needed = SandboxBroker::MAY_WRITE; 455 break; 456 case O_RDWR: 457 needed = SandboxBroker::MAY_READ | SandboxBroker::MAY_WRITE; 458 break; 459 default: 460 return false; 461 } 462 if (aReqFlags & O_CREAT) { 463 needed |= SandboxBroker::MAY_CREATE; 464 } 465 // Linux allows O_TRUNC even with O_RDONLY 466 if (aReqFlags & O_TRUNC) { 467 needed |= SandboxBroker::MAY_WRITE; 468 } 469 return (aPerms & needed) == needed; 470 } 471 472 static int DoStat(const char* aPath, statstruct* aBuff, int aFlags) { 473 if (aFlags & O_NOFOLLOW) { 474 return lstatsyscall(aPath, aBuff); 475 } 476 return statsyscall(aPath, aBuff); 477 } 478 479 static int DoLink(const char* aPath, const char* aPath2, 480 SandboxBrokerCommon::Operation aOper) { 481 if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_LINK) { 482 return link(aPath, aPath2); 483 } 484 if (aOper == SandboxBrokerCommon::Operation::SANDBOX_FILE_SYMLINK) { 485 return symlink(aPath, aPath2); 486 } 487 MOZ_CRASH("SandboxBroker: Unknown link operation"); 488 } 489 490 static int DoConnect(const char* aPath, size_t aLen, int aType, 491 bool aIsAbstract) { 492 // Deny SOCK_DGRAM for the same reason it's denied for socketpair. 493 if (aType != SOCK_STREAM && aType != SOCK_SEQPACKET) { 494 errno = EACCES; 495 return -1; 496 } 497 // Ensure that the address is a pathname. (An empty string 498 // resulting from an abstract address probably shouldn't have made 499 // it past the policy check, but check explicitly just in case.) 500 if (aPath[0] == '\0') { 501 errno = ENETUNREACH; 502 return -1; 503 } 504 505 // Try to copy the name into a normal-sized sockaddr_un, with 506 // null-termination. Specifically, from man page: 507 // 508 // When the address of an abstract socket is returned, the returned addrlen is 509 // greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of 510 // the socket is contained in the first (addrlen - sizeof(sa_family_t)) bytes 511 // of sun_path. 512 // 513 // As mentionned in `SandboxBrokerClient::Connect()`, `DoCall` expects a 514 // null-terminated string while abstract socket are not. So we receive a copy 515 // here and we have to put things back correctly as a real abstract socket to 516 // perform the brokered `connect()` call. 517 struct sockaddr_un sun; 518 memset(&sun, 0, sizeof(sun)); 519 sun.sun_family = AF_UNIX; 520 char* sunPath = sun.sun_path; 521 size_t sunLen = sizeof(sun.sun_path); 522 size_t addrLen = sizeof(sun); 523 if (aIsAbstract) { 524 *sunPath++ = '\0'; 525 sunLen--; 526 addrLen = offsetof(struct sockaddr_un, sun_path) + aLen + 1; 527 } 528 if (aLen + 1 > sunLen) { 529 errno = ENAMETOOLONG; 530 return -1; 531 } 532 memcpy(sunPath, aPath, aLen); 533 534 // Finally, the actual socket connection. 535 const int fd = socket(AF_UNIX, aType | SOCK_CLOEXEC, 0); 536 if (fd < 0) { 537 return -1; 538 } 539 if (connect(fd, reinterpret_cast<struct sockaddr*>(&sun), addrLen) < 0) { 540 close(fd); 541 return -1; 542 } 543 return fd; 544 } 545 546 size_t SandboxBroker::RealPath(char* aPath, size_t aBufSize, size_t aPathLen) { 547 char* result = realpath(aPath, nullptr); 548 if (result != nullptr) { 549 base::strlcpy(aPath, result, aBufSize); 550 free(result); 551 // Size changed, but guaranteed to be 0 terminated 552 aPathLen = strlen(aPath); 553 } 554 return aPathLen; 555 } 556 557 size_t SandboxBroker::ConvertRelativePath(char* aPath, size_t aBufSize, 558 size_t aPathLen) { 559 if (strstr(aPath, "..") != nullptr) { 560 return RealPath(aPath, aBufSize, aPathLen); 561 } 562 return aPathLen; 563 } 564 565 nsCString SandboxBroker::ReverseSymlinks(const nsACString& aPath) { 566 // Revert any symlinks we previously resolved. 567 int32_t cutLength = aPath.Length(); 568 nsCString cutPath(Substring(aPath, 0, cutLength)); 569 570 for (;;) { 571 nsCString orig; 572 bool found = mSymlinkMap.Get(cutPath, &orig); 573 if (found) { 574 orig.Append(Substring(aPath, cutLength, aPath.Length() - cutLength)); 575 return orig; 576 } 577 // Not found? Remove a path component and try again. 578 int32_t pos = cutPath.RFindChar('/'); 579 if (pos == kNotFound || pos <= 0) { 580 // will be empty 581 return orig; 582 } else { 583 // Cut until just before the / 584 cutLength = pos; 585 cutPath.Assign(Substring(cutPath, 0, cutLength)); 586 } 587 } 588 } 589 590 int SandboxBroker::SymlinkPermissions(const char* aPath, 591 const size_t aPathLen) { 592 // Work on a temporary copy, so we can reverse it. 593 // Because we bail on a writable dir, SymlinkPath 594 // might not restore the callers' path exactly. 595 char pathBufSymlink[kMaxPathLen + 1]; 596 strcpy(pathBufSymlink, aPath); 597 598 nsCString orig = 599 ReverseSymlinks(nsDependentCString(pathBufSymlink, aPathLen)); 600 if (!orig.IsEmpty()) { 601 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 602 SANDBOX_LOG("Reversing %s -> %s", aPath, orig.get()); 603 } 604 base::strlcpy(pathBufSymlink, orig.get(), sizeof(pathBufSymlink)); 605 } 606 607 int perms = 0; 608 // Resolve relative paths, propagate permissions and 609 // fail if a symlink is in a writable path. The output is in perms. 610 char* result = 611 SandboxBroker::SymlinkPath(mPolicy.get(), pathBufSymlink, NULL, &perms); 612 if (result != NULL) { 613 free(result); 614 // We finished the translation, so we have a usable return in "perms". 615 return perms; 616 } else { 617 // Empty path means we got a writable dir in the chain or tried 618 // to back out of a link target. 619 return 0; 620 } 621 } 622 623 void SandboxBroker::ThreadMain(void) { 624 // Create a nsThread wrapper for the current platform thread, and register it 625 // with the thread manager. 626 (void)NS_GetCurrentThread(); 627 628 char threadName[kThreadNameMaxSize]; 629 // mChildPid can be max 7 digits because of the previous string size, 630 // and 'FSBroker' is 8 bytes. The maximum thread size is 16 with the null byte 631 // included. That leaves us 7 digits. 632 SprintfLiteral(threadName, "FSBroker%d", mChildPid); 633 PlatformThread::SetName(threadName); 634 635 AUTO_PROFILER_REGISTER_THREAD(threadName); 636 637 // The gtest SandboxBrokerMisc.* will loop and create / destroy SandboxBroker 638 // taking a RefPtr<SandboxBroker> that gets destructed when getting out of 639 // scope. 640 // 641 // Because Create() will start this thread we get into a situation where: 642 // - SandboxBrokerMisc creates SandboxBroker 643 // RefPtr = 1 644 // - SandboxBrokerMisc gtest is leaving the scope so destructor got called 645 // RefPtr = 0 646 // - this thread starts and add a new reference via that RefPtr 647 // RefPtr = 1 648 // - destructor does its job and closes the FD which triggers EOF and ends 649 // the while {} here 650 // - thread ends and gets out of scope so destructor gets called 651 // RefPtr = 0 652 // 653 // NS_ADDREF_THIS() / dont_AddRef(this) avoid this. 654 RefPtr<SandboxBroker> deathGrip = dont_AddRef(this); 655 656 // Permissive mode can only be enabled through an environment variable, 657 // therefore it is sufficient to fetch the value once 658 // before the main thread loop starts 659 bool permissive = SandboxInfo::Get().Test(SandboxInfo::kPermissive); 660 661 while (true) { 662 struct iovec ios[2]; 663 // We will receive the path strings in 1 buffer and split them back up. 664 char recvBuf[2 * (kMaxPathLen + 1)]; 665 char pathBuf[kMaxPathLen + 1]; 666 char pathBuf2[kMaxPathLen + 1]; 667 size_t pathLen = 0; 668 size_t pathLen2 = 0; 669 char respBuf[kMaxPathLen + 1]; // Also serves as struct stat 670 Request req; 671 Response resp; 672 int respfd; 673 674 // Make sure stat responses fit in the response buffer 675 MOZ_ASSERT((kMaxPathLen + 1) > sizeof(struct stat)); 676 677 // This makes our string handling below a bit less error prone. 678 memset(recvBuf, 0, sizeof(recvBuf)); 679 680 ios[0].iov_base = &req; 681 ios[0].iov_len = sizeof(req); 682 ios[1].iov_base = recvBuf; 683 ios[1].iov_len = sizeof(recvBuf); 684 685 const ssize_t recvd = RecvWithFd(mFileDesc, ios, 2, &respfd); 686 if (recvd == 0) { 687 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 688 SANDBOX_LOG("EOF from pid %d", mChildPid); 689 } 690 break; 691 } 692 // It could be possible to continue after errors and short reads, 693 // at least in some cases, but protocol violation indicates a 694 // hostile client, so terminate the broker instead. 695 if (recvd < 0) { 696 SANDBOX_LOG_ERRNO("bad read from pid %d", mChildPid); 697 shutdown(mFileDesc, SHUT_RD); 698 break; 699 } 700 if (recvd < static_cast<ssize_t>(sizeof(req))) { 701 SANDBOX_LOG("bad read from pid %d (%d < %d)", mChildPid, recvd, 702 sizeof(req)); 703 shutdown(mFileDesc, SHUT_RD); 704 break; 705 } 706 if (respfd == -1) { 707 SANDBOX_LOG("no response fd from pid %d", mChildPid); 708 shutdown(mFileDesc, SHUT_RD); 709 break; 710 } 711 712 // Initialize the response with the default failure. 713 memset(&resp, 0, sizeof(resp)); 714 memset(&respBuf, 0, sizeof(respBuf)); 715 resp.mError = -EACCES; 716 ios[0].iov_base = &resp; 717 ios[0].iov_len = sizeof(resp); 718 ios[1].iov_base = nullptr; 719 ios[1].iov_len = 0; 720 int openedFd = -1; 721 722 // Clear permissions 723 int perms; 724 725 // Find end of first string, make sure the buffer is still 726 // 0 terminated. 727 size_t recvBufLen = static_cast<size_t>(recvd) - sizeof(req); 728 if (recvBufLen > 0 && recvBuf[recvBufLen - 1] != 0) { 729 SANDBOX_LOG("corrupted path buffer from pid %d", mChildPid); 730 shutdown(mFileDesc, SHUT_RD); 731 break; 732 } 733 734 // First path should fit in maximum path length buffer. 735 size_t first_len = strlen(recvBuf); 736 if (first_len <= kMaxPathLen) { 737 strcpy(pathBuf, recvBuf); 738 // Skip right over the terminating 0, and try to copy in the 739 // second path, if any. If there's no path, this will hit a 740 // 0 immediately (we nulled the buffer before receiving). 741 // We do not assume the second path is 0-terminated, this is 742 // enforced below. 743 strncpy(pathBuf2, recvBuf + first_len + 1, kMaxPathLen); 744 745 // First string is guaranteed to be 0-terminated. 746 pathLen = first_len; 747 748 // Look up the first pathname but first translate relative paths. 749 pathLen = ConvertRelativePath(pathBuf, sizeof(pathBuf), pathLen); 750 perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen)); 751 752 if (!perms) { 753 // Did we arrive from a symlink in a path that is not writable? 754 // Then try to figure out the original path and see if that is 755 // readable. Work on the original path, this reverses 756 // ConvertRelative above. 757 int symlinkPerms = SymlinkPermissions(recvBuf, first_len); 758 if (symlinkPerms > 0) { 759 perms = symlinkPerms; 760 } 761 } 762 if (!perms) { 763 // Now try the opposite case: translate symlinks to their 764 // actual destination file. Firefox always resolves symlinks, 765 // and in most cases we have whitelisted fixed paths that 766 // libraries will rely on and try to open. So this codepath 767 // is mostly useful for Mesa which had its kernel interface 768 // moved around. 769 pathLen = RealPath(pathBuf, sizeof(pathBuf), pathLen); 770 perms = mPolicy->Lookup(nsDependentCString(pathBuf, pathLen)); 771 } 772 773 // Same for the second path. 774 pathLen2 = strnlen(pathBuf2, kMaxPathLen); 775 if (pathLen2 > 0) { 776 // Force 0 termination. 777 pathBuf2[pathLen2] = '\0'; 778 pathLen2 = ConvertRelativePath(pathBuf2, sizeof(pathBuf2), pathLen2); 779 int perms2 = mPolicy->Lookup(nsDependentCString(pathBuf2, pathLen2)); 780 781 // Take the intersection of the permissions for both paths. 782 perms &= perms2; 783 } 784 } else { 785 // Failed to receive intelligible paths. 786 perms = 0; 787 } 788 789 // And now perform the operation if allowed. 790 if (perms & CRASH_INSTEAD) { 791 // This is somewhat nonmodular, but it works. 792 resp.mError = -ENOSYS; 793 } else if ((perms & FORCE_DENY) == FORCE_DENY) { 794 resp.mError = -EACCES; 795 } else if (permissive || perms & MAY_ACCESS) { 796 // If the operation was only allowed because of permissive mode, log it. 797 if (permissive && !(perms & MAY_ACCESS)) { 798 AuditPermissive(req.mOp, req.mFlags, req.mId, perms, pathBuf); 799 } 800 801 switch (req.mOp) { 802 case SANDBOX_FILE_OPEN: 803 if (permissive || AllowOpen(req.mFlags, perms)) { 804 // Permissions for O_CREAT hardwired to 0600; if that's 805 // ever a problem we can change the protocol (but really we 806 // should be trying to remove uses of MAY_CREATE, not add 807 // new ones). 808 openedFd = open(pathBuf, req.mFlags | kRequiredOpenFlags, 0600); 809 if (openedFd >= 0) { 810 resp.mError = 0; 811 } else { 812 resp.mError = -errno; 813 } 814 } else { 815 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 816 } 817 break; 818 819 case SANDBOX_FILE_ACCESS: 820 if (permissive || AllowAccess(req.mFlags, perms)) { 821 if (access(pathBuf, req.mFlags) == 0) { 822 resp.mError = 0; 823 } else { 824 resp.mError = -errno; 825 } 826 } else { 827 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 828 } 829 break; 830 831 case SANDBOX_FILE_STAT: 832 MOZ_ASSERT(req.mBufSize == sizeof(statstruct)); 833 if (DoStat(pathBuf, (statstruct*)&respBuf, req.mFlags) == 0) { 834 resp.mError = 0; 835 ios[1].iov_base = &respBuf; 836 ios[1].iov_len = sizeof(statstruct); 837 } else { 838 resp.mError = -errno; 839 } 840 break; 841 842 case SANDBOX_FILE_CHMOD: 843 if (permissive || AllowOperation(W_OK, perms)) { 844 if (chmod(pathBuf, req.mFlags) == 0) { 845 resp.mError = 0; 846 } else { 847 resp.mError = -errno; 848 } 849 } else { 850 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 851 } 852 break; 853 854 case SANDBOX_FILE_LINK: 855 case SANDBOX_FILE_SYMLINK: 856 if (permissive || AllowOperation(W_OK | X_OK, perms)) { 857 if (DoLink(pathBuf, pathBuf2, req.mOp) == 0) { 858 resp.mError = 0; 859 } else { 860 resp.mError = -errno; 861 } 862 } else { 863 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 864 } 865 break; 866 867 case SANDBOX_FILE_RENAME: 868 if (permissive || AllowOperation(W_OK | X_OK, perms)) { 869 if (rename(pathBuf, pathBuf2) == 0) { 870 resp.mError = 0; 871 } else { 872 resp.mError = -errno; 873 } 874 } else { 875 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 876 } 877 break; 878 879 case SANDBOX_FILE_MKDIR: 880 if (permissive || AllowOperation(W_OK | X_OK, perms)) { 881 if (mkdir(pathBuf, req.mFlags) == 0) { 882 resp.mError = 0; 883 } else { 884 resp.mError = -errno; 885 } 886 } else { 887 struct stat sb; 888 // This doesn't need an additional policy check because 889 // MAY_ACCESS is required to even enter this switch statement. 890 if (lstat(pathBuf, &sb) == 0) { 891 resp.mError = -EEXIST; 892 } else { 893 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 894 } 895 } 896 break; 897 898 case SANDBOX_FILE_UNLINK: 899 if (permissive || AllowOperation(W_OK | X_OK, perms)) { 900 if (unlink(pathBuf) == 0) { 901 resp.mError = 0; 902 } else { 903 resp.mError = -errno; 904 } 905 } else { 906 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 907 } 908 break; 909 910 case SANDBOX_FILE_RMDIR: 911 if (permissive || AllowOperation(W_OK | X_OK, perms)) { 912 if (rmdir(pathBuf) == 0) { 913 resp.mError = 0; 914 } else { 915 resp.mError = -errno; 916 } 917 } else { 918 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 919 } 920 break; 921 922 case SANDBOX_FILE_READLINK: 923 if (permissive || AllowOperation(R_OK, perms)) { 924 ssize_t respSize = 925 readlink(pathBuf, (char*)&respBuf, sizeof(respBuf)); 926 if (respSize >= 0) { 927 if (respSize > 0) { 928 // Record the mapping so we can invert the file to the original 929 // symlink. 930 nsDependentCString orig(pathBuf, pathLen); 931 nsDependentCString xlat(respBuf, respSize); 932 if (!orig.Equals(xlat) && xlat[0] == '/') { 933 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 934 SANDBOX_LOG("Recording mapping %s -> %s", xlat.get(), 935 orig.get()); 936 } 937 mSymlinkMap.InsertOrUpdate(xlat, orig); 938 } 939 // Make sure we can invert a fully resolved mapping too. If our 940 // caller is realpath, and there's a relative path involved, the 941 // client side will try to open this one. 942 char* resolvedBuf = realpath(pathBuf, nullptr); 943 if (resolvedBuf) { 944 nsDependentCString resolvedXlat(resolvedBuf); 945 if (!orig.Equals(resolvedXlat) && 946 !xlat.Equals(resolvedXlat)) { 947 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 948 SANDBOX_LOG("Recording mapping %s -> %s", 949 resolvedXlat.get(), orig.get()); 950 } 951 mSymlinkMap.InsertOrUpdate(resolvedXlat, orig); 952 } 953 free(resolvedBuf); 954 } 955 } 956 // Truncate the reply to the size of the client's 957 // buffer, matching the real readlink()'s behavior in 958 // that case, and being careful with the input data. 959 ssize_t callerSize = 960 std::max(AssertedCast<ssize_t>(req.mBufSize), ssize_t(0)); 961 respSize = std::min(respSize, callerSize); 962 resp.mError = AssertedCast<int>(respSize); 963 ios[1].iov_base = &respBuf; 964 ios[1].iov_len = ReleaseAssertedCast<size_t>(respSize); 965 MOZ_RELEASE_ASSERT(ios[1].iov_len <= sizeof(respBuf)); 966 } else { 967 resp.mError = -errno; 968 } 969 } else { 970 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 971 } 972 break; 973 974 case SANDBOX_SOCKET_CONNECT: 975 case SANDBOX_SOCKET_CONNECT_ABSTRACT: 976 if (permissive || (perms & MAY_CONNECT) != 0) { 977 openedFd = DoConnect(pathBuf, pathLen, req.mFlags, 978 req.mOp == SANDBOX_SOCKET_CONNECT_ABSTRACT); 979 if (openedFd >= 0) { 980 resp.mError = 0; 981 } else { 982 resp.mError = -errno; 983 } 984 } else { 985 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 986 } 987 break; 988 } 989 } else { 990 MOZ_ASSERT(perms == 0); 991 AuditDenial(req.mOp, req.mFlags, req.mId, perms, pathBuf); 992 } 993 994 const size_t numIO = ios[1].iov_len > 0 ? 2 : 1; 995 const ssize_t sent = SendWithFd(respfd, ios, numIO, openedFd); 996 if (sent < 0) { 997 SANDBOX_LOG_ERRNO("failed to send broker response to pid %d", mChildPid); 998 } else { 999 MOZ_ASSERT(static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len); 1000 } 1001 1002 // Work around Linux kernel bug: recvmsg checks for pending data 1003 // and then checks for EOF or shutdown, without synchronization; 1004 // if the sendmsg and last close occur between those points, it 1005 // will see no pending data (before) and a closed socket (after), 1006 // and incorrectly return EOF even though there is a message to be 1007 // read. To avoid this, we send an extra message with a reference 1008 // to respfd, so the last close can't happen until after the real 1009 // response is read. 1010 // 1011 // See also: https://bugzil.la/1243108#c48 1012 const struct Response fakeResp = {-4095}; 1013 const struct iovec fakeIO = {const_cast<Response*>(&fakeResp), 1014 sizeof(fakeResp)}; 1015 // If the client has already read the real response and closed its 1016 // socket then this will fail, but that's fine. 1017 if (SendWithFd(respfd, &fakeIO, 1, respfd) < 0) { 1018 MOZ_ASSERT(errno == EPIPE || errno == ECONNREFUSED || errno == ENOTCONN); 1019 } 1020 1021 close(respfd); 1022 1023 if (openedFd >= 0) { 1024 close(openedFd); 1025 } 1026 } 1027 } 1028 1029 void SandboxBroker::AuditPermissive(int aOp, int aFlags, uint64_t aId, 1030 int aPerms, const char* aPath) { 1031 MOZ_RELEASE_ASSERT(SandboxInfo::Get().Test(SandboxInfo::kPermissive)); 1032 1033 struct stat statBuf; 1034 1035 if (lstat(aPath, &statBuf) == 0) { 1036 // Path exists, set errno to 0 to indicate "success". 1037 errno = 0; 1038 } 1039 1040 SANDBOX_LOG_ERRNO( 1041 "SandboxBroker: would have denied op=%s rflags=%o perms=%d path=%s for " 1042 "pid=%d permissive=1; real status", 1043 OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid); 1044 SandboxProfiler::ReportAudit("SandboxBroker::AuditPermissive", 1045 OperationDescription[aOp], aFlags, aId, aPerms, 1046 aPath, mChildPid); 1047 } 1048 1049 void SandboxBroker::AuditDenial(int aOp, int aFlags, uint64_t aId, int aPerms, 1050 const char* aPath) { 1051 if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { 1052 SANDBOX_LOG( 1053 "SandboxBroker: denied op=%s rflags=%o perms=%d path=%s for pid=%d", 1054 OperationDescription[aOp], aFlags, aPerms, aPath, mChildPid); 1055 } 1056 SandboxProfiler::ReportAudit("SandboxBroker::AuditDenial", 1057 OperationDescription[aOp], aFlags, aId, aPerms, 1058 aPath, mChildPid); 1059 } 1060 1061 } // namespace mozilla