/* -*- 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 #include #include #include #include #include #include #include "mozilla/ArrayUtils.h" #include "mozilla/NullPtr.h" #include "mozilla/unused.h" #ifdef MOZ_CRASHREPORTER #include "nsExceptionHandler.h" #endif #if defined(ANDROID) #include "android_ucontext.h" #include #endif #include "seccomp_filter.h" #include "mozilla/dom/Exceptions.h" #include "nsString.h" #include "nsThreadUtils.h" #include "linux_seccomp.h" #ifdef MOZ_LOGGING #define FORCE_PR_LOG 1 #endif #include "prlog.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 struct sock_filter seccomp_filter[] = { VALIDATE_ARCHITECTURE, EXAMINE_SYSCALL, SECCOMP_WHITELIST, #ifdef MOZ_CONTENT_SANDBOX_REPORTER TRAP_PROCESS, #else KILL_PROCESS, #endif }; struct sock_fprog seccomp_prog = { (unsigned short)MOZ_ARRAY_LENGTH(seccomp_filter), seccomp_filter, }; /** * 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) { nsAutoCString 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)" : funName.get(), fileName.IsVoid() ? "(no file)" : 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) { if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { return 1; } if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &seccomp_prog, 0, 0)) { return 1; } return 0; } /** * Starts the seccomp sandbox for this process. * Generally called just after SetCurrentProcessPrivileges. * Should be called only once, and before any potentially harmful content is loaded. * * Should normally make the process exit on failure. */ void SetCurrentProcessSandbox(void) { #if !defined(ANDROID) && defined(PR_LOGGING) if (!gSeccompSandboxLog) { gSeccompSandboxLog = PR_NewLogModule("SeccompSandbox"); } PR_ASSERT(gSeccompSandboxLog); #endif #ifdef MOZ_CONTENT_SANDBOX_REPORTER if (InstallSyscallReporter()) { LOG_ERROR("install_syscall_reporter() failed\n"); /* This is disabled so that we do not exit if seccomp-bpf is not available * This will be re-enabled when all B2G devices are required to support seccomp-bpf * See bug 880797 for reversal */ /* _exit(127); */ } #endif if (InstallSyscallFilter()) { LOG_ERROR("install_syscall_filter() failed\n"); /* This is disabled so that we do not exit if seccomp-bpf is not available * This will be re-enabled when all B2G devices are required to support seccomp-bpf * See bug 880797 for reversal */ /* _exit(127); */ } } } // namespace mozilla