diff -r 000000000000 -r 6474c204b198 security/sandbox/linux/Sandbox.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/security/sandbox/linux/Sandbox.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,427 @@ +/* -*- 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 "mozilla/Sandbox.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mozilla/Atomics.h" +#include "mozilla/NullPtr.h" +#include "mozilla/unused.h" +#include "mozilla/dom/Exceptions.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#if defined(ANDROID) +#include "android_ucontext.h" +#include +#endif + +#if defined(MOZ_CONTENT_SANDBOX) +#include "linux_seccomp.h" +#include "SandboxFilter.h" +#endif + +#ifdef MOZ_LOGGING +#define FORCE_PR_LOG 1 +#endif +#include "prlog.h" +#include "prenv.h" + +namespace mozilla { +#if defined(ANDROID) +#define LOG_ERROR(args...) __android_log_print(ANDROID_LOG_ERROR, "Sandbox", ## args) +#elif defined(PR_LOGGING) +static PRLogModuleInfo* gSeccompSandboxLog; +#define LOG_ERROR(args...) PR_LOG(gSeccompSandboxLog, PR_LOG_ERROR, (args)) +#else +#define LOG_ERROR(args...) +#endif + +/** + * Log JS stack info in the same place as the sandbox violation + * message. Useful in case the responsible code is JS and all we have + * are logs and a minidump with the C++ stacks (e.g., on TBPL). + */ +static void +SandboxLogJSStack(void) +{ + if (!NS_IsMainThread()) { + // This might be a worker thread... or it might be a non-JS + // thread, or a non-NSPR thread. There's isn't a good API for + // dealing with this, yet. + return; + } + nsCOMPtr frame = dom::GetCurrentJSStack(); + for (int i = 0; frame != nullptr; ++i) { + nsAutoString fileName, funName; + int32_t lineNumber; + + // Don't stop unwinding if an attribute can't be read. + fileName.SetIsVoid(true); + unused << frame->GetFilename(fileName); + lineNumber = 0; + unused << frame->GetLineNumber(&lineNumber); + funName.SetIsVoid(true); + unused << frame->GetName(funName); + + if (!funName.IsVoid() || !fileName.IsVoid()) { + LOG_ERROR("JS frame %d: %s %s line %d", i, + funName.IsVoid() ? + "(anonymous)" : NS_ConvertUTF16toUTF8(funName).get(), + fileName.IsVoid() ? + "(no file)" : NS_ConvertUTF16toUTF8(fileName).get(), + lineNumber); + } + + nsCOMPtr nextFrame; + nsresult rv = frame->GetCaller(getter_AddRefs(nextFrame)); + NS_ENSURE_SUCCESS_VOID(rv); + frame = nextFrame; + } +} + +/** + * 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. + */ +#ifdef MOZ_CONTENT_SANDBOX_REPORTER +static void +Reporter(int nr, siginfo_t *info, void *void_context) +{ + ucontext_t *ctx = static_cast(void_context); + unsigned long syscall_nr, args[6]; + pid_t pid = getpid(), tid = syscall(__NR_gettid); + + 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); + + 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]); + +#ifdef MOZ_CRASHREPORTER + bool dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context); + if (!dumped) { + LOG_ERROR("Failed to write minidump"); + } +#endif + + // 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); + _exit(127); +} + +/** + * The reporter is called when the process receives a SIGSYS signal. + * The signal is sent by the kernel when Seccomp encounter a system call + * that has not been allowed. + * We register an action for that signal (calling the Reporter function). + * + * This function should not be used in production and thus generally be + * called from debug code. In production, the process is directly killed. + * For this reason, the function is ifdef'd, as there is no reason to + * compile it while unused. + * + * @return 0 on success, -1 on failure. + * @see Reporter() function. + */ +static int +InstallSyscallReporter(void) +{ + struct sigaction act; + sigset_t mask; + memset(&act, 0, sizeof(act)); + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + act.sa_sigaction = &Reporter; + act.sa_flags = SA_SIGINFO | SA_NODEFER; + if (sigaction(SIGSYS, &act, nullptr) < 0) { + return -1; + } + if (sigemptyset(&mask) || + sigaddset(&mask, SIGSYS) || + sigprocmask(SIG_UNBLOCK, &mask, nullptr)) { + return -1; + } + return 0; +} +#endif + +/** + * 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). + * + * @return 0 on success, 1 on failure. + * @see sock_fprog (the seccomp_prog). + */ +static int +InstallSyscallFilter(void) +{ +#ifdef MOZ_DMD + char* e = PR_GetEnv("DMD"); + if (e && strcmp(e, "") != 0 && strcmp(e, "0") != 0) { + LOG_ERROR("SANDBOX DISABLED FOR DMD! See bug 956961."); + // Must treat this as "failure" in order to prevent infinite loop; + // cf. the PR_GET_SECCOMP check below. + return 1; + } +#endif + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + return 1; + } + + const sock_fprog *filter = GetSandboxFilter(); + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)filter, 0, 0)) { + return 1; + } + return 0; +} + +// 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 sSetSandboxDone; +// about:memory has the first 3 RT signals. (We should allocate +// signals centrally instead of hard-coding them like this.) +static const int sSetSandboxSignum = SIGRTMIN + 3; + +static bool +SetThreadSandbox() +{ + bool didAnything = false; + + if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX") == nullptr && + prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) { + if (InstallSyscallFilter() == 0) { + didAnything = true; + } + /* + * Bug 880797: when all B2G devices are required to support + * seccomp-bpf, this should exit/crash if InstallSyscallFilter + * returns nonzero (ifdef MOZ_WIDGET_GONK). + */ + } + return didAnything; +} + +static void +SetThreadSandboxHandler(int signum) +{ + // The non-zero number sent back to the main thread indicates + // whether action was taken. + if (SetThreadSandbox()) { + sSetSandboxDone = 2; + } else { + sSetSandboxDone = 1; + } + // Wake up the main thread. See the FUTEX_WAIT call, below, for an + // explanation. + syscall(__NR_futex, reinterpret_cast(&sSetSandboxDone), + FUTEX_WAKE, 1); +} + +static void +BroadcastSetThreadSandbox() +{ + pid_t pid, tid; + DIR *taskdp; + struct dirent *de; + + static_assert(sizeof(mozilla::Atomic) == sizeof(int), + "mozilla::Atomic isn't represented by an int"); + MOZ_ASSERT(NS_IsMainThread()); + pid = getpid(); + taskdp = opendir("/proc/self/task"); + if (taskdp == nullptr) { + LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno)); + MOZ_CRASH(); + } + if (signal(sSetSandboxSignum, SetThreadSandboxHandler) != SIG_DFL) { + LOG_ERROR("signal %d in use!\n", sSetSandboxSignum); + 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 == pid) { + // Drop the main thread's privileges last, below, so + // we can continue to signal other threads. + continue; + } + // Reset the futex cell and signal. + sSetSandboxDone = 0; + if (syscall(__NR_tgkill, pid, tid, sSetSandboxSignum) != 0) { + if (errno == ESRCH) { + LOG_ERROR("Thread %d unexpectedly exited.", tid); + // Rescan threads, in case it forked before exiting. + sandboxProgress = true; + continue; + } + 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 sSetSandboxDone == 0, then sleep. + if (syscall(__NR_futex, reinterpret_cast(&sSetSandboxDone), + FUTEX_WAIT, 0, &futexTimeout) != 0) { + if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) { + LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno)); + MOZ_CRASH(); + } + } + // Did the handler finish? + if (sSetSandboxDone > 0) { + if (sSetSandboxDone == 2) { + sandboxProgress = true; + } + break; + } + // Has the thread ceased to exist? + if (syscall(__NR_tgkill, pid, tid, 0) != 0) { + if (errno == ESRCH) { + 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_nsec || + (now.tv_sec == timeLimit.tv_nsec && + now.tv_nsec > timeLimit.tv_nsec)) { + LOG_ERROR("Thread %d unresponsive for %d seconds. Killing process.", + tid, crashDelay); + MOZ_CRASH(); + } + } + } + rewinddir(taskdp); + } while (sandboxProgress); + unused << signal(sSetSandboxSignum, SIG_DFL); + unused << closedir(taskdp); + // And now, deprivilege the main thread: + SetThreadSandbox(); +} + +// This function can overapproximate (i.e., return true even if +// sandboxing isn't supported, but not the reverse). See bug 993145. +static bool +IsSandboxingSupported(void) +{ + return prctl(PR_GET_SECCOMP) != -1; +} + +/** + * Starts the seccomp sandbox for this process and sets user/group-based privileges. + * Should be called only once, and before any potentially harmful content is loaded. + * + * Should normally make the process exit on failure. +*/ +void +SetCurrentProcessSandbox() +{ +#if !defined(ANDROID) && defined(PR_LOGGING) + if (!gSeccompSandboxLog) { + gSeccompSandboxLog = PR_NewLogModule("SeccompSandbox"); + } + PR_ASSERT(gSeccompSandboxLog); +#endif + +#if defined(MOZ_CONTENT_SANDBOX_REPORTER) + if (InstallSyscallReporter()) { + LOG_ERROR("install_syscall_reporter() failed\n"); + } +#endif + + if (IsSandboxingSupported()) { + BroadcastSetThreadSandbox(); + } +} + +} // namespace mozilla +