static int OpenDeletedDirectory() { // We don't need this directory to persist between invocations of // the program (nor need it to be cleaned up if something goes wrong // here, because mkdtemp will choose a fresh name), so /tmp as // specified by FHS is adequate. char path[] = "/tmp/mozsandbox.XXXXXX"; if (!mkdtemp(path)) { SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno)); return -1; } int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY)); if (fd < 0) { SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno)); // Try to clean up. Shouldn't fail, but livable if it does. DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0; MOZ_ASSERT(ok); return -1; } if (HANDLE_EINTR(rmdir(path)) != 0) { SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno)); AlwaysClose(fd); return -1; } return fd; }
bool SandboxChroot::Prepare() { LinuxCapabilities caps; if (!caps.GetCurrent() || !caps.Effective(CAP_SYS_CHROOT)) { SANDBOX_LOG_ERROR("don't have permission to chroot"); return false; } mFd = OpenDeletedDirectory(); if (mFd < 0) { SANDBOX_LOG_ERROR("failed to create empty directory for chroot"); return false; } MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex)); MOZ_ASSERT(mCommand == NO_THREAD); if (pthread_create(&mThread, nullptr, StaticThreadMain, this) != 0) { MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex)); SANDBOX_LOG_ERROR("pthread_create: %s", strerror(errno)); return false; } while (mCommand != NO_COMMAND) { MOZ_ASSERT(mCommand == NO_THREAD); MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex)); } MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex)); return true; }
/** * This is the SIGSYS handler function. It is used to report to the user * which system call has been denied by Seccomp. * This function also makes the process exit as denying the system call * will otherwise generally lead to unexpected behavior from the process, * since we don't know if all functions will handle such denials gracefully. * * @see InstallSyscallReporter() function. */ static void Reporter(int nr, siginfo_t *info, void *void_context) { ucontext_t *ctx = static_cast<ucontext_t*>(void_context); unsigned long syscall_nr, args[6]; pid_t pid = getpid(); if (nr != SIGSYS) { return; } if (info->si_code != SYS_SECCOMP) { return; } if (!ctx) { return; } syscall_nr = SECCOMP_SYSCALL(ctx); args[0] = SECCOMP_PARM1(ctx); args[1] = SECCOMP_PARM2(ctx); args[2] = SECCOMP_PARM3(ctx); args[3] = SECCOMP_PARM4(ctx); args[4] = SECCOMP_PARM5(ctx); args[5] = SECCOMP_PARM6(ctx); #ifdef MOZ_GMP_SANDBOX if (syscall_nr == __NR_open && gMediaPluginFilePath) { const char *path = reinterpret_cast<const char*>(args[0]); int flags = int(args[1]); if ((flags & O_ACCMODE) != O_RDONLY) { SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)", path, flags); } else if (strcmp(path, gMediaPluginFilePath) != 0) { SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin" " %s", path, gMediaPluginFilePath); } else if (gMediaPluginFileDesc == -1) { SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented"); } else { SECCOMP_RESULT(ctx) = gMediaPluginFileDesc; gMediaPluginFileDesc = -1; return; } } #endif SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %lu," " args %lu %lu %lu %lu %lu %lu. Killing process.", pid, syscall_nr, args[0], args[1], args[2], args[3], args[4], args[5]); // Bug 1017393: record syscall number somewhere useful. info->si_addr = reinterpret_cast<void*>(syscall_nr); gSandboxCrashFunc(nr, info, void_context); _exit(127); }
static bool ChrootToFileDesc(int fd) { if (fchdir(fd) != 0) { SANDBOX_LOG_ERROR("fchdir: %s", strerror(errno)); return false; } if (chroot(".") != 0) { SANDBOX_LOG_ERROR("chroot: %s", strerror(errno)); return false; } return true; }
/** * This function installs the syscall filter, a.k.a. seccomp. * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more * syscalls to the process beyond this point (even after fork()). * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows * to pass a bpf program (in our case, it contains a syscall * whitelist). * * Reports failure by crashing. * * @see sock_fprog (the seccomp_prog). */ static void InstallSyscallFilter(const sock_fprog *prog) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { SANDBOX_LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno)); MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)"); } if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)prog, 0, 0)) { SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s", strerror(errno)); MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)"); } }
// Common code for sandbox startup. static void SetCurrentProcessSandbox(UniquePtr<sandbox::bpf_dsl::Policy> aPolicy) { MOZ_ASSERT(gSandboxCrashFunc); // Note: PolicyCompiler borrows the policy and registry for its // lifetime, but does not take ownership of them. sandbox::bpf_dsl::PolicyCompiler compiler(aPolicy.get(), sandbox::Trap::Registry()); auto program = compiler.Compile(); if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { sandbox::bpf_dsl::DumpBPF::PrintProgram(*program); } InstallSigSysHandler(); #ifdef MOZ_ASAN __sanitizer_sandbox_arguments asanArgs; asanArgs.coverage_sandboxed = 1; asanArgs.coverage_fd = -1; asanArgs.coverage_max_block_size = 0; __sanitizer_sandbox_on_notify(&asanArgs); #endif // The syscall takes a C-style array, so copy the vector into one. size_t programLen = program->size(); UniquePtr<sock_filter[]> flatProgram(new sock_filter[programLen]); for (auto i = program->begin(); i != program->end(); ++i) { flatProgram[i - program->begin()] = *i; } sock_fprog fprog; fprog.filter = flatProgram.get(); fprog.len = static_cast<unsigned short>(programLen); MOZ_RELEASE_ASSERT(static_cast<size_t>(fprog.len) == programLen); const SandboxInfo info = SandboxInfo::Get(); if (info.Test(SandboxInfo::kHasSeccompTSync)) { if (info.Test(SandboxInfo::kVerbose)) { SANDBOX_LOG_ERROR("using seccomp tsync"); } ApplySandboxWithTSync(&fprog); } else { if (info.Test(SandboxInfo::kVerbose)) { SANDBOX_LOG_ERROR("no tsync support; using signal broadcast"); } BroadcastSetThreadSandbox(&fprog); } MOZ_RELEASE_ASSERT(!gChrootHelper, "forgot to chroot"); }
static void AlwaysClose(int fd) { if (IGNORE_EINTR(close(fd)) != 0) { SANDBOX_LOG_ERROR("close: %s", strerror(errno)); MOZ_CRASH("failed to close()"); } }
int SandboxOpenedFiles::GetDesc(const char* aPath) const { for (const auto& file : mFiles) { if (strcmp(file.Path(), aPath) == 0) { return file.GetDesc(); } } SANDBOX_LOG_ERROR("attempt to open unexpected file %s", aPath); return -1; }
int SandboxOpenedFile::GetDesc() const { int fd = -1; if (mDup) { fd = mMaybeFd; if (fd >= 0) { fd = dup(fd); if (fd < 0) { SANDBOX_LOG_ERROR("dup: %s", strerror(errno)); } } } else { fd = TakeDesc(); } if (fd < 0 && !mExpectError) { SANDBOX_LOG_ERROR("unexpected multiple open of file %s", Path()); } return fd; }
// Common code for sandbox startup. static void SetCurrentProcessSandbox(SandboxType aType) { MOZ_ASSERT(gSandboxCrashFunc); if (InstallSyscallReporter()) { SANDBOX_LOG_ERROR("install_syscall_reporter() failed\n"); } BroadcastSetThreadSandbox(aType); }
static intptr_t OpenTrap(const sandbox::arch_seccomp_data& aArgs, void* aux) { auto plugin = static_cast<SandboxOpenedFile*>(aux); const char* path; int flags; switch (aArgs.nr) { #ifdef __NR_open case __NR_open: path = reinterpret_cast<const char*>(aArgs.args[0]); flags = static_cast<int>(aArgs.args[1]); break; #endif case __NR_openat: // The path has to be absolute to match the pre-opened file (see // assertion in ctor) so the dirfd argument is ignored. path = reinterpret_cast<const char*>(aArgs.args[1]); flags = static_cast<int>(aArgs.args[2]); break; default: MOZ_CRASH("unexpected syscall number"); } if ((flags & O_ACCMODE) != O_RDONLY) { SANDBOX_LOG_ERROR("non-read-only open of file %s attempted (flags=0%o)", path, flags); return -ENOSYS; } if (strcmp(path, plugin->mPath) != 0) { SANDBOX_LOG_ERROR("attempt to open file %s which is not the media plugin" " %s", path, plugin->mPath); return -ENOSYS; } int fd = plugin->mFd.exchange(-1); if (fd < 0) { SANDBOX_LOG_ERROR("multiple opens of media plugin file unimplemented"); return -ENOSYS; } return fd; }
static void SandboxLogCStack() { // Skip 3 frames: one for this module, one for the signal handler in // libmozsandbox, and one for the signal trampoline. // // Warning: this might not print any stack frames. MozStackWalk // can't walk past the signal trampoline on ARM (bug 968531), and // x86 frame pointer walking may or may not work (bug 1082276). MozStackWalk(SandboxPrintStackFrame, /* skip */ 3, /* max */ 0, nullptr); SANDBOX_LOG_ERROR("end of stack."); }
static int OpenDeletedDirectory() { // We don't need this directory to persist between invocations of // the program (nor need it to be cleaned up if something goes wrong // here, because mkdtemp will choose a fresh name), so /tmp as // specified by FHS is adequate. // // However, this needs a filesystem where a deleted directory can // still be used, and /tmp is sometimes not that; e.g., aufs(5), // often used for containers, will cause the chroot() to fail with // ESTALE (bug 1162965). So this uses /dev/shm if possible instead. char tmpPath[] = "/tmp/mozsandbox.XXXXXX"; char shmPath[] = "/dev/shm/mozsandbox.XXXXXX"; char* path; if (mkdtemp(shmPath)) { path = shmPath; } else if (mkdtemp(tmpPath)) { path = tmpPath; } else { SANDBOX_LOG_ERROR("mkdtemp: %s", strerror(errno)); return -1; } int fd = HANDLE_EINTR(open(path, O_RDONLY | O_DIRECTORY)); if (fd < 0) { SANDBOX_LOG_ERROR("open %s: %s", path, strerror(errno)); // Try to clean up. Shouldn't fail, but livable if it does. DebugOnly<bool> ok = HANDLE_EINTR(rmdir(path)) == 0; MOZ_ASSERT(ok); return -1; } if (HANDLE_EINTR(rmdir(path)) != 0) { SANDBOX_LOG_ERROR("rmdir %s: %s", path, strerror(errno)); AlwaysClose(fd); return -1; } return fd; }
/** * This function installs the syscall filter, a.k.a. seccomp. The * aUseTSync flag indicates whether this should apply to all threads * in the process -- which will fail if the kernel doesn't support * that -- or only the current thread. * * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows * to pass a bpf program (in our case, it contains a syscall * whitelist). * * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more * syscalls to the process beyond this point (even after fork()), and * prevents gaining capabilities (e.g., by exec'ing a setuid root * program). The kernel won't allow seccomp-bpf without doing this, * because otherwise it could be used for privilege escalation attacks. * * Returns false (and sets errno) on failure. * * @see SandboxInfo * @see BroadcastSetThreadSandbox */ static bool MOZ_WARN_UNUSED_RESULT InstallSyscallFilter(const sock_fprog *aProg, bool aUseTSync) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { SANDBOX_LOG_ERROR("prctl(PR_SET_NO_NEW_PRIVS) failed: %s", strerror(errno)); MOZ_CRASH("prctl(PR_SET_NO_NEW_PRIVS)"); } if (aUseTSync) { if (syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, aProg) != 0) { SANDBOX_LOG_ERROR("thread-synchronized seccomp failed: %s", strerror(errno)); return false; } } else { if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)aProg, 0, 0)) { SANDBOX_LOG_ERROR("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER) failed: %s", strerror(errno)); return false; } } return true; }
/** * Starts the seccomp sandbox for a media plugin process. Should be * called only once, and before any potentially harmful content is * loaded -- including the plugin itself, if it's considered untrusted. * * The file indicated by aFilePath, if non-null, can be open()ed once * read-only after the sandbox starts; it should be the .so file * implementing the not-yet-loaded plugin. * * Will normally make the process exit on failure. */ void SetMediaPluginSandbox(const char *aFilePath) { if (gSandboxFlags.isDisabledForGMP) { return; } if (aFilePath) { gMediaPluginFilePath = strdup(aFilePath); gMediaPluginFileDesc = open(aFilePath, O_RDONLY | O_CLOEXEC); if (gMediaPluginFileDesc == -1) { SANDBOX_LOG_ERROR("failed to open plugin file %s: %s", aFilePath, strerror(errno)); MOZ_CRASH(); } } // Finally, start the sandbox. SetCurrentProcessSandbox(kSandboxMediaPlugin); }
/** * This is the SIGSYS handler function. It delegates to the Chromium * TrapRegistry handler (see InstallSigSysHandler, below) and, if the * trap handler installed by the policy would fail with ENOSYS, * crashes the process. This allows unintentional policy failures to * be reported as crash dumps and fixed. It also logs information * about the failed system call. * * Note that this could be invoked in parallel on multiple threads and * that it could be in async signal context (e.g., intercepting an * open() called from an async signal handler). */ static void SigSysHandler(int nr, siginfo_t *info, void *void_context) { ucontext_t *ctx = static_cast<ucontext_t*>(void_context); // This shouldn't ever be null, but the Chromium handler checks for // that and refrains from crashing, so let's not crash release builds: MOZ_DIAGNOSTIC_ASSERT(ctx); if (!ctx) { return; } // Save a copy of the context before invoking the trap handler, // which will overwrite one or more registers with the return value. ucontext_t savedCtx = *ctx; gChromiumSigSysHandler(nr, info, ctx); if (!ContextIsError(ctx, ENOSYS)) { return; } pid_t pid = getpid(); unsigned long syscall_nr = SECCOMP_SYSCALL(&savedCtx); unsigned long args[6]; args[0] = SECCOMP_PARM1(&savedCtx); args[1] = SECCOMP_PARM2(&savedCtx); args[2] = SECCOMP_PARM3(&savedCtx); args[3] = SECCOMP_PARM4(&savedCtx); args[4] = SECCOMP_PARM5(&savedCtx); args[5] = SECCOMP_PARM6(&savedCtx); // TODO, someday when this is enabled on MIPS: include the two extra // args in the error message. SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %d," " args %d %d %d %d %d %d. Killing process.", pid, syscall_nr, args[0], args[1], args[2], args[3], args[4], args[5]); // Bug 1017393: record syscall number somewhere useful. info->si_addr = reinterpret_cast<void*>(syscall_nr); gSandboxCrashFunc(nr, info, &savedCtx); _exit(127); }
// Common code for sandbox startup. static void SetCurrentProcessSandbox(SandboxType aType) { MOZ_ASSERT(gSandboxCrashFunc); if (InstallSyscallReporter()) { SANDBOX_LOG_ERROR("install_syscall_reporter() failed\n"); } #ifdef MOZ_ASAN __sanitizer_sandbox_arguments asanArgs; asanArgs.coverage_sandboxed = 1; asanArgs.coverage_fd = -1; asanArgs.coverage_max_block_size = 0; __sanitizer_sandbox_on_notify(&asanArgs); #endif BroadcastSetThreadSandbox(aType); }
static void SandboxCrash(int nr, siginfo_t *info, void *void_context) { pid_t pid = getpid(), tid = syscall(__NR_gettid); bool dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context); if (!dumped) { SANDBOX_LOG_ERROR("crash reporter is disabled (or failed);" " trying stack trace:"); SandboxLogCStack(); } // Do this last, in case it crashes or deadlocks. SandboxLogJSStack(); // Try to reraise, so the parent sees that this process crashed. // (If tgkill is forbidden, then seccomp will raise SIGSYS, which // also accomplishes that goal.) signal(SIGSYS, SIG_DFL); syscall(__NR_tgkill, pid, tid, nr); }
void SandboxChroot::ThreadMain() { // First, drop everything that isn't CAP_SYS_CHROOT. (This code // assumes that this thread already has effective CAP_SYS_CHROOT, // because Prepare() checked for it before creating this thread.) LinuxCapabilities caps; caps.Effective(CAP_SYS_CHROOT) = true; if (!caps.SetCurrent()) { SANDBOX_LOG_ERROR("capset: %s", strerror(errno)); MOZ_CRASH("Can't limit chroot thread's capabilities"); } MOZ_ALWAYS_ZERO(pthread_mutex_lock(&mMutex)); MOZ_ASSERT(mCommand == NO_THREAD); mCommand = NO_COMMAND; MOZ_ALWAYS_ZERO(pthread_cond_signal(&mWakeup)); while (mCommand == NO_COMMAND) { MOZ_ALWAYS_ZERO(pthread_cond_wait(&mWakeup, &mMutex)); } if (mCommand == DO_CHROOT) { MOZ_ASSERT(mFd >= 0); if (!ChrootToFileDesc(mFd)) { MOZ_CRASH("Failed to chroot"); } } else { MOZ_ASSERT(mCommand == JUST_EXIT); } if (mFd >= 0) { AlwaysClose(mFd); mFd = -1; } mCommand = NO_THREAD; MOZ_ALWAYS_ZERO(pthread_mutex_unlock(&mMutex)); // Drop the remaining capabilities; see note in SandboxChroot.h // about the potential unreliability of pthread_join. if (!LinuxCapabilities().SetCurrent()) { MOZ_CRASH("can't drop capabilities"); } }
/** * Starts the seccomp sandbox for a media plugin process. Should be * called only once, and before any potentially harmful content is * loaded -- including the plugin itself, if it's considered untrusted. * * The file indicated by aFilePath, if non-null, can be open()ed * read-only, once, after the sandbox starts; it should be the .so * file implementing the not-yet-loaded plugin. * * Will normally make the process exit on failure. */ void SetMediaPluginSandbox(const char *aFilePath) { if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) { return; } MOZ_ASSERT(!gMediaPluginFile.mPath); if (aFilePath) { gMediaPluginFile.mPath = strdup(aFilePath); gMediaPluginFile.mFd = open(aFilePath, O_RDONLY | O_CLOEXEC); if (gMediaPluginFile.mFd == -1) { SANDBOX_LOG_ERROR("failed to open plugin file %s: %s", aFilePath, strerror(errno)); MOZ_CRASH(); } } else { gMediaPluginFile.mFd = -1; } // Finally, start the sandbox. SetCurrentProcessSandbox(GetMediaSandboxPolicy(&gMediaPluginFile)); }
/* static */ void SandboxInfo::ThreadingCheck() { // Allow MOZ_SANDBOX_UNEXPECTED_THREADS to be set manually for testing. if (IsSingleThreaded() && !getenv("MOZ_SANDBOX_UNEXPECTED_THREADS")) { return; } SANDBOX_LOG_ERROR("unexpected multithreading found; this prevents using" " namespace sandboxing.%s", // getenv isn't thread-safe, but see below. getenv("LD_PRELOAD") ? " (If you're LD_PRELOAD'ing" " nVidia GL: that's not necessary for Gecko.)" : ""); // Propagate this information for use by child processes. (setenv // isn't thread-safe, but other threads are from non-Gecko code so // they wouldn't be using NSPR; we have to hope for the best.) setenv("MOZ_SANDBOX_UNEXPECTED_THREADS", "1", 0); int flags = sSingleton.mFlags; flags |= kUnexpectedThreads; flags &= ~(kHasUserNamespaces | kHasPrivilegedUserNamespaces); sSingleton.mFlags = static_cast<Flags>(flags); }
void SandboxEarlyInit(GeckoProcessType aType, bool aIsNuwa) { // Bug 1168555: Nuwa isn't reliably single-threaded at this point; // it starts an IPC I/O thread and then shuts it down before calling // the plugin-container entry point, but that thread may not have // finished exiting. If/when any type of sandboxing is used for the // Nuwa process (e.g., unsharing the network namespace there instead // of for each content process, to save memory), this will need to be // changed by moving the SandboxEarlyInit call to an earlier point. if (aIsNuwa) { return; } MOZ_RELEASE_ASSERT(IsSingleThreaded()); const SandboxInfo info = SandboxInfo::Get(); // Which kinds of resource isolation (of those that need to be set // up at this point) can be used by this process? bool canChroot = false; bool canUnshareNet = false; bool canUnshareIPC = false; switch (aType) { case GeckoProcessType_Default: MOZ_ASSERT(false, "SandboxEarlyInit in parent process"); return; #ifdef MOZ_GMP_SANDBOX case GeckoProcessType_GMPlugin: if (!info.Test(SandboxInfo::kEnabledForMedia)) { break; } canUnshareNet = true; canUnshareIPC = true; // Need seccomp-bpf to intercept open(). canChroot = info.Test(SandboxInfo::kHasSeccompBPF); break; #endif // In the future, content processes will be able to use some of // these. default: // Other cases intentionally left blank. break; } // If there's nothing to do, then we're done. if (!canChroot && !canUnshareNet && !canUnshareIPC) { return; } { LinuxCapabilities existingCaps; if (existingCaps.GetCurrent() && existingCaps.AnyEffective()) { SANDBOX_LOG_ERROR("PLEASE DO NOT RUN THIS AS ROOT. Strange things may" " happen when capabilities are dropped."); } } // If capabilities can't be gained, then nothing can be done. if (!info.Test(SandboxInfo::kHasUserNamespaces)) { // Drop any existing capabilities; unsharing the user namespace // would implicitly drop them, so if we're running in a broken // configuration where that would matter (e.g., running as root // from a non-root-owned mode-0700 directory) this means it will // break the same way on all kernels and be easier to troubleshoot. LinuxCapabilities().SetCurrent(); return; } // The failure cases for the various unshares, and setting up the // chroot helper, don't strictly need to be fatal -- but they also // shouldn't fail on any reasonable system, so let's take the small // risk of breakage over the small risk of quietly providing less // security than we expect. (Unlike in SandboxInfo, this is in the // child process, so crashing here isn't as severe a response to the // unexpected.) if (!UnshareUserNamespace()) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWUSER): %s", strerror(errno)); // If CanCreateUserNamespace (SandboxInfo.cpp) returns true, then // the unshare shouldn't have failed. MOZ_CRASH("unshare(CLONE_NEWUSER)"); } // No early returns after this point! We need to drop the // capabilities that were gained by unsharing the user namesapce. if (canUnshareIPC && syscall(__NR_unshare, CLONE_NEWIPC) != 0) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWIPC): %s", strerror(errno)); MOZ_CRASH("unshare(CLONE_NEWIPC)"); } if (canUnshareNet && syscall(__NR_unshare, CLONE_NEWNET) != 0) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWNET): %s", strerror(errno)); MOZ_CRASH("unshare(CLONE_NEWNET)"); } if (canChroot) { gChrootHelper = MakeUnique<SandboxChroot>(); if (!gChrootHelper->Prepare()) { SANDBOX_LOG_ERROR("failed to set up chroot helper"); MOZ_CRASH("SandboxChroot::Prepare"); } } if (!LinuxCapabilities().SetCurrent()) { SANDBOX_LOG_ERROR("dropping capabilities: %s", strerror(errno)); MOZ_CRASH("can't drop capabilities"); } }
static void BroadcastSetThreadSandbox(const sock_fprog* aFilter) { int signum; pid_t pid, tid, myTid; DIR *taskdp; struct dirent *de; // This function does not own *aFilter, so this global needs to // always be zeroed before returning. gSetSandboxFilter = aFilter; static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int), "mozilla::Atomic<int> isn't represented by an int"); pid = getpid(); myTid = syscall(__NR_gettid); taskdp = opendir("/proc/self/task"); if (taskdp == nullptr) { SANDBOX_LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno)); MOZ_CRASH(); } EnterChroot(); signum = FindFreeSignalNumber(); if (signum == 0) { SANDBOX_LOG_ERROR("No available signal numbers!"); MOZ_CRASH(); } void (*oldHandler)(int); oldHandler = signal(signum, SetThreadSandboxHandler); if (oldHandler != SIG_DFL) { // See the comment on FindFreeSignalNumber about race conditions. SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n", signum, oldHandler); MOZ_CRASH(); } // In case this races with a not-yet-deprivileged thread cloning // itself, repeat iterating over all threads until we find none // that are still privileged. bool sandboxProgress; do { sandboxProgress = false; // For each thread... while ((de = readdir(taskdp))) { char *endptr; tid = strtol(de->d_name, &endptr, 10); if (*endptr != '\0' || tid <= 0) { // Not a task ID. continue; } if (tid == myTid) { // Drop this thread's privileges last, below, so we can // continue to signal other threads. continue; } // Reset the futex cell and signal. gSetSandboxDone = 0; if (syscall(__NR_tgkill, pid, tid, signum) != 0) { if (errno == ESRCH) { SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid); // Rescan threads, in case it forked before exiting. sandboxProgress = true; continue; } SANDBOX_LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno)); MOZ_CRASH(); } // It's unlikely, but if the thread somehow manages to exit // after receiving the signal but before entering the signal // handler, we need to avoid blocking forever. // // Using futex directly lets the signal handler send the wakeup // from an async signal handler (pthread mutex/condvar calls // aren't allowed), and to use a relative timeout that isn't // affected by changes to the system clock (not possible with // POSIX semaphores). // // If a thread doesn't respond within a reasonable amount of // time, but still exists, we crash -- the alternative is either // blocking forever or silently losing security, and it // shouldn't actually happen. static const int crashDelay = 10; // seconds struct timespec timeLimit; clock_gettime(CLOCK_MONOTONIC, &timeLimit); timeLimit.tv_sec += crashDelay; while (true) { static const struct timespec futexTimeout = { 0, 10*1000*1000 }; // 10ms // Atomically: if gSetSandboxDone == 0, then sleep. if (syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone), FUTEX_WAIT, 0, &futexTimeout) != 0) { if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) { SANDBOX_LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno)); MOZ_CRASH(); } } // Did the handler finish? if (gSetSandboxDone > 0) { if (gSetSandboxDone == 2) { sandboxProgress = true; } break; } // Has the thread ceased to exist? if (syscall(__NR_tgkill, pid, tid, 0) != 0) { if (errno == ESRCH) { SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid); } // Rescan threads, in case it forked before exiting. // Also, if it somehow failed in a way that wasn't ESRCH, // and still exists, that will be handled on the next pass. sandboxProgress = true; break; } struct timespec now; clock_gettime(CLOCK_MONOTONIC, &now); if (now.tv_sec > timeLimit.tv_sec || (now.tv_sec == timeLimit.tv_sec && now.tv_nsec > timeLimit.tv_nsec)) { SANDBOX_LOG_ERROR("Thread %d unresponsive for %d seconds." " Killing process.", tid, crashDelay); MOZ_CRASH(); } } } rewinddir(taskdp); } while (sandboxProgress); oldHandler = signal(signum, SIG_DFL); if (oldHandler != SetThreadSandboxHandler) { // See the comment on FindFreeSignalNumber about race conditions. SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!", signum, oldHandler); MOZ_CRASH(); } Unused << closedir(taskdp); // And now, deprivilege the main thread: SetThreadSandbox(); gSetSandboxFilter = nullptr; }
int SandboxBrokerClient::DoCall(const Request* aReq, const char* aPath, struct stat* aStat, bool expectFd) { // Remap /proc/self to the actual pid, so that the broker can open // it. This happens here instead of in the broker to follow the // principle of least privilege and keep the broker as simple as // possible. (Note: when pid namespaces happen, this will also need // to remap the inner pid to the outer pid.) static const char kProcSelf[] = "/proc/self/"; static const size_t kProcSelfLen = sizeof(kProcSelf) - 1; const char* path = aPath; // This buffer just needs to be large enough for any such path that // the policy would actually allow. sizeof("/proc/2147483647/") == 18. char rewrittenPath[64]; if (strncmp(aPath, kProcSelf, kProcSelfLen) == 0) { ssize_t len = base::strings::SafeSPrintf(rewrittenPath, "/proc/%d/%s", getpid(), aPath + kProcSelfLen); if (static_cast<size_t>(len) < sizeof(rewrittenPath)) { if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { SANDBOX_LOG_ERROR("rewriting %s -> %s", aPath, rewrittenPath); } path = rewrittenPath; } else { SANDBOX_LOG_ERROR("not rewriting unexpectedly long path %s", aPath); } } struct iovec ios[2]; int respFds[2]; // Set up iovecs for request + path. ios[0].iov_base = const_cast<Request*>(aReq); ios[0].iov_len = sizeof(*aReq); ios[1].iov_base = const_cast<char*>(path); ios[1].iov_len = strlen(path); if (ios[1].iov_len > kMaxPathLen) { return -ENAMETOOLONG; } // Create response socket and send request. if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, respFds) < 0) { return -errno; } const ssize_t sent = SendWithFd(mFileDesc, ios, 2, respFds[1]); const int sendErrno = errno; MOZ_ASSERT(sent < 0 || static_cast<size_t>(sent) == ios[0].iov_len + ios[1].iov_len); close(respFds[1]); if (sent < 0) { close(respFds[0]); return -sendErrno; } // Set up iovecs for response. Response resp; ios[0].iov_base = &resp; ios[0].iov_len = sizeof(resp); if (aStat) { ios[1].iov_base = aStat; ios[1].iov_len = sizeof(*aStat); } else { ios[1].iov_base = nullptr; ios[1].iov_len = 0; } // Wait for response and return appropriately. int openedFd = -1; const ssize_t recvd = RecvWithFd(respFds[0], ios, aStat ? 2 : 1, expectFd ? &openedFd : nullptr); const int recvErrno = errno; close(respFds[0]); if (recvd < 0) { return -recvErrno; } if (recvd == 0) { SANDBOX_LOG_ERROR("Unexpected EOF, op %d flags 0%o path %s", aReq->mOp, aReq->mFlags, path); return -EIO; } if (resp.mError != 0) { // If the operation fails, the return payload will be empty; // adjust the iov_len for the following assertion. ios[1].iov_len = 0; } MOZ_ASSERT(static_cast<size_t>(recvd) == ios[0].iov_len + ios[1].iov_len); if (resp.mError == 0) { // Success! if (expectFd) { MOZ_ASSERT(openedFd >= 0); return openedFd; } return 0; } if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) { // Keep in mind that "rejected" files can include ones that don't // actually exist, if it's something that's optional or part of a // search path (e.g., shared libraries). In those cases, this // error message is expected. SANDBOX_LOG_ERROR("Rejected errno %d op %d flags 0%o path %s", resp.mError, aReq->mOp, aReq->mFlags, path); } if (openedFd >= 0) { close(openedFd); } return -resp.mError; }
void SandboxEarlyInit(GeckoProcessType aType) { const SandboxInfo info = SandboxInfo::Get(); if (info.Test(SandboxInfo::kUnexpectedThreads)) { return; } MOZ_RELEASE_ASSERT(IsSingleThreaded()); // Which kinds of resource isolation (of those that need to be set // up at this point) can be used by this process? bool canChroot = false; bool canUnshareNet = false; bool canUnshareIPC = false; switch (aType) { case GeckoProcessType_Default: MOZ_ASSERT(false, "SandboxEarlyInit in parent process"); return; #ifdef MOZ_GMP_SANDBOX case GeckoProcessType_GMPlugin: if (!info.Test(SandboxInfo::kEnabledForMedia)) { break; } canUnshareNet = true; canUnshareIPC = true; // Need seccomp-bpf to intercept open(). canChroot = info.Test(SandboxInfo::kHasSeccompBPF); break; #endif // In the future, content processes will be able to use some of // these. default: // Other cases intentionally left blank. break; } // If TSYNC is not supported, set up signal handler // used to enable seccomp on each thread. if (!info.Test(SandboxInfo::kHasSeccompTSync)) { gSeccompTsyncBroadcastSignum = FindFreeSignalNumber(); if (gSeccompTsyncBroadcastSignum == 0) { SANDBOX_LOG_ERROR("No available signal numbers!"); MOZ_CRASH(); } void (*oldHandler)(int); oldHandler = signal(gSeccompTsyncBroadcastSignum, SetThreadSandboxHandler); if (oldHandler != SIG_DFL) { // See the comment on FindFreeSignalNumber about race conditions. SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n", gSeccompTsyncBroadcastSignum, oldHandler); MOZ_CRASH(); } } // If there's nothing to do, then we're done. if (!canChroot && !canUnshareNet && !canUnshareIPC) { return; } { LinuxCapabilities existingCaps; if (existingCaps.GetCurrent() && existingCaps.AnyEffective()) { SANDBOX_LOG_ERROR("PLEASE DO NOT RUN THIS AS ROOT. Strange things may" " happen when capabilities are dropped."); } } // If capabilities can't be gained, then nothing can be done. if (!info.Test(SandboxInfo::kHasUserNamespaces)) { // Drop any existing capabilities; unsharing the user namespace // would implicitly drop them, so if we're running in a broken // configuration where that would matter (e.g., running as root // from a non-root-owned mode-0700 directory) this means it will // break the same way on all kernels and be easier to troubleshoot. LinuxCapabilities().SetCurrent(); return; } // The failure cases for the various unshares, and setting up the // chroot helper, don't strictly need to be fatal -- but they also // shouldn't fail on any reasonable system, so let's take the small // risk of breakage over the small risk of quietly providing less // security than we expect. (Unlike in SandboxInfo, this is in the // child process, so crashing here isn't as severe a response to the // unexpected.) if (!UnshareUserNamespace()) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWUSER): %s", strerror(errno)); // If CanCreateUserNamespace (SandboxInfo.cpp) returns true, then // the unshare shouldn't have failed. MOZ_CRASH("unshare(CLONE_NEWUSER)"); } // No early returns after this point! We need to drop the // capabilities that were gained by unsharing the user namesapce. if (canUnshareIPC && syscall(__NR_unshare, CLONE_NEWIPC) != 0) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWIPC): %s", strerror(errno)); MOZ_CRASH("unshare(CLONE_NEWIPC)"); } if (canUnshareNet && syscall(__NR_unshare, CLONE_NEWNET) != 0) { SANDBOX_LOG_ERROR("unshare(CLONE_NEWNET): %s", strerror(errno)); MOZ_CRASH("unshare(CLONE_NEWNET)"); } if (canChroot) { gChrootHelper = MakeUnique<SandboxChroot>(); if (!gChrootHelper->Prepare()) { SANDBOX_LOG_ERROR("failed to set up chroot helper"); MOZ_CRASH("SandboxChroot::Prepare"); } } if (!LinuxCapabilities().SetCurrent()) { SANDBOX_LOG_ERROR("dropping capabilities: %s", strerror(errno)); MOZ_CRASH("can't drop capabilities"); } }