diff options
Diffstat (limited to 'security/sandbox/linux/Sandbox.cpp')
-rw-r--r-- | security/sandbox/linux/Sandbox.cpp | 609 |
1 files changed, 0 insertions, 609 deletions
diff --git a/security/sandbox/linux/Sandbox.cpp b/security/sandbox/linux/Sandbox.cpp deleted file mode 100644 index 80a18f855..000000000 --- a/security/sandbox/linux/Sandbox.cpp +++ /dev/null @@ -1,609 +0,0 @@ -/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* vim: set ts=8 sts=2 et sw=2 tw=80: */ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#include "Sandbox.h" - -#include "LinuxCapabilities.h" -#include "LinuxSched.h" -#include "SandboxBrokerClient.h" -#include "SandboxChroot.h" -#include "SandboxFilter.h" -#include "SandboxInternal.h" -#include "SandboxLogging.h" -#include "SandboxUtil.h" - -#include <dirent.h> -#include <errno.h> -#include <fcntl.h> -#include <linux/futex.h> -#include <pthread.h> -#include <signal.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/prctl.h> -#include <sys/ptrace.h> -#include <sys/syscall.h> -#include <sys/time.h> -#include <unistd.h> - -#include "mozilla/Atomics.h" -#include "mozilla/Maybe.h" -#include "mozilla/SandboxInfo.h" -#include "mozilla/UniquePtr.h" -#include "mozilla/Unused.h" -#include "sandbox/linux/bpf_dsl/codegen.h" -#include "sandbox/linux/bpf_dsl/dump_bpf.h" -#include "sandbox/linux/bpf_dsl/policy.h" -#include "sandbox/linux/bpf_dsl/policy_compiler.h" -#include "sandbox/linux/bpf_dsl/seccomp_macros.h" -#include "sandbox/linux/seccomp-bpf/trap.h" -#include "sandbox/linux/system_headers/linux_filter.h" -#include "sandbox/linux/system_headers/linux_seccomp.h" -#include "sandbox/linux/system_headers/linux_syscalls.h" -#if defined(ANDROID) -#include "sandbox/linux/system_headers/linux_ucontext.h" -#endif - -#ifdef MOZ_ASAN -// Copy libsanitizer declarations to avoid depending on ASAN headers. -// See also bug 1081242 comment #4. -extern "C" { -namespace __sanitizer { -// Win64 uses long long, but this is Linux. -typedef signed long sptr; -} // namespace __sanitizer - -typedef struct { - int coverage_sandboxed; - __sanitizer::sptr coverage_fd; - unsigned int coverage_max_block_size; -} __sanitizer_sandbox_arguments; - -MOZ_IMPORT_API void -__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args); -} // extern "C" -#endif // MOZ_ASAN - -// Signal number used to enable seccomp on each thread. -int gSeccompTsyncBroadcastSignum = 0; - -namespace mozilla { - -// This is initialized by SandboxSetCrashFunc(). -SandboxCrashFunc gSandboxCrashFunc; - -static UniquePtr<SandboxChroot> gChrootHelper; -static void (*gChromiumSigSysHandler)(int, siginfo_t*, void*); - -// Test whether a ucontext, interpreted as the state after a syscall, -// indicates the given error. See also sandbox::Syscall::PutValueInUcontext. -static bool -ContextIsError(const ucontext_t *aContext, int aError) -{ - // Avoid integer promotion warnings. (The unary addition makes - // the decltype not evaluate to a reference type.) - typedef decltype(+SECCOMP_RESULT(aContext)) reg_t; - -#ifdef __mips__ - return SECCOMP_PARM4(aContext) != 0 - && SECCOMP_RESULT(aContext) == static_cast<reg_t>(aError); -#else - return SECCOMP_RESULT(aContext) == static_cast<reg_t>(-aError); -#endif -} - -/** - * 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); -} - -/** - * This function installs the SIGSYS handler. This is slightly - * complicated because we want to use Chromium's handler to dispatch - * to specific trap handlers defined in the policy, but we also need - * the full original signal context to give to Breakpad for crash - * dumps. So we install Chromium's handler first, then retrieve its - * address so our replacement can delegate to it. - */ -static void -InstallSigSysHandler(void) -{ - struct sigaction act; - - // Ensure that the Chromium handler is installed. - Unused << sandbox::Trap::Registry(); - - // If the signal handling state isn't as expected, crash now instead - // of crashing later (and more confusingly) when SIGSYS happens. - - if (sigaction(SIGSYS, nullptr, &act) != 0) { - MOZ_CRASH("Couldn't read old SIGSYS disposition"); - } - if ((act.sa_flags & SA_SIGINFO) != SA_SIGINFO) { - MOZ_CRASH("SIGSYS not already set to a siginfo handler?"); - } - MOZ_RELEASE_ASSERT(act.sa_sigaction); - gChromiumSigSysHandler = act.sa_sigaction; - act.sa_sigaction = SigSysHandler; - // Currently, SA_NODEFER should already be set by the Chromium code, - // but it's harmless to ensure that it's set: - MOZ_ASSERT(act.sa_flags & SA_NODEFER); - act.sa_flags |= SA_NODEFER; - if (sigaction(SIGSYS, &act, nullptr) < 0) { - MOZ_CRASH("Couldn't change SIGSYS disposition"); - } -} - -/** - * 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 if the filter was already installed (see the - * PR_SET_NO_NEW_PRIVS rule in SandboxFilter.cpp). Crashes on any - * other error condition. - * - * @see SandboxInfo - * @see BroadcastSetThreadSandbox - */ -static bool MOZ_MUST_USE -InstallSyscallFilter(const sock_fprog *aProg, bool aUseTSync) -{ - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { - if (!aUseTSync && errno == ETXTBSY) { - return false; - } - 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)); - MOZ_CRASH("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)"); - } - } 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)); - MOZ_CRASH("seccomp+tsync failed, but kernel supports tsync"); - } - } - return true; -} - -// Use signals for permissions that need to be set per-thread. -// The communication channel from the signal handler back to the main thread. -static mozilla::Atomic<int> gSetSandboxDone; -// Pass the filter itself through a global. -const sock_fprog* gSetSandboxFilter; - -// We have to dynamically allocate the signal number; see bug 1038900. -// This function returns the first realtime signal currently set to -// default handling (i.e., not in use), or 0 if none could be found. -// -// WARNING: if this function or anything similar to it (including in -// external libraries) is used on multiple threads concurrently, there -// will be a race condition. -static int -FindFreeSignalNumber() -{ - for (int signum = SIGRTMAX; signum >= SIGRTMIN; --signum) { - struct sigaction sa; - - if (sigaction(signum, nullptr, &sa) == 0 && - (sa.sa_flags & SA_SIGINFO) == 0 && - sa.sa_handler == SIG_DFL) { - return signum; - } - } - return 0; -} - -// Returns true if sandboxing was enabled, or false if sandboxing -// already was enabled. Crashes if sandboxing could not be enabled. -static bool -SetThreadSandbox() -{ - return InstallSyscallFilter(gSetSandboxFilter, false); -} - -static void -SetThreadSandboxHandler(int signum) -{ - // The non-zero number sent back to the main thread indicates - // whether action was taken. - if (SetThreadSandbox()) { - gSetSandboxDone = 2; - } else { - gSetSandboxDone = 1; - } - // Wake up the main thread. See the FUTEX_WAIT call, below, for an - // explanation. - syscall(__NR_futex, reinterpret_cast<int*>(&gSetSandboxDone), - FUTEX_WAKE, 1); -} - -static void -EnterChroot() -{ - if (gChrootHelper) { - gChrootHelper->Invoke(); - gChrootHelper = nullptr; - } -} - -static void -BroadcastSetThreadSandbox(const sock_fprog* aFilter) -{ - 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(); - - // 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; - } - - MOZ_RELEASE_ASSERT(gSeccompTsyncBroadcastSignum != 0); - - // Reset the futex cell and signal. - gSetSandboxDone = 0; - if (syscall(__NR_tgkill, pid, tid, gSeccompTsyncBroadcastSignum) != 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); - - void (*oldHandler)(int); - oldHandler = signal(gSeccompTsyncBroadcastSignum, SIG_DFL); - gSeccompTsyncBroadcastSignum = 0; - if (oldHandler != SetThreadSandboxHandler) { - // See the comment on FindFreeSignalNumber about race conditions. - SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!", - gSeccompTsyncBroadcastSignum, oldHandler); - MOZ_CRASH(); - } - Unused << closedir(taskdp); - // And now, deprivilege the main thread: - SetThreadSandbox(); - gSetSandboxFilter = nullptr; -} - -static void -ApplySandboxWithTSync(sock_fprog* aFilter) -{ - EnterChroot(); - // At this point we're committed to using tsync, because the signal - // broadcast workaround needs to access procfs. (Unless chroot - // isn't used... but this failure shouldn't happen in the first - // place, so let's not make extra special cases for it.) - if (!InstallSyscallFilter(aFilter, true)) { - MOZ_CRASH(); - } -} - -// 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()); - sandbox::CodeGen::Program 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"); -} - -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; - 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"); - } -} - -} // namespace mozilla |