security/sandbox/linux/Sandbox.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     7 #include "mozilla/Sandbox.h"
     9 #include <unistd.h>
    10 #include <stdio.h>
    11 #include <sys/ptrace.h>
    12 #include <sys/prctl.h>
    13 #include <sys/syscall.h>
    14 #include <signal.h>
    15 #include <string.h>
    16 #include <linux/futex.h>
    17 #include <sys/time.h>
    18 #include <dirent.h>
    19 #include <stdlib.h>
    20 #include <pthread.h>
    21 #include <errno.h>
    23 #include "mozilla/Atomics.h"
    24 #include "mozilla/NullPtr.h"
    25 #include "mozilla/unused.h"
    26 #include "mozilla/dom/Exceptions.h"
    27 #include "nsString.h"
    28 #include "nsThreadUtils.h"
    30 #ifdef MOZ_CRASHREPORTER
    31 #include "nsExceptionHandler.h"
    32 #endif
    34 #if defined(ANDROID)
    35 #include "android_ucontext.h"
    36 #include <android/log.h>
    37 #endif
    39 #if defined(MOZ_CONTENT_SANDBOX)
    40 #include "linux_seccomp.h"
    41 #include "SandboxFilter.h"
    42 #endif
    44 #ifdef MOZ_LOGGING
    45 #define FORCE_PR_LOG 1
    46 #endif
    47 #include "prlog.h"
    48 #include "prenv.h"
    50 namespace mozilla {
    51 #if defined(ANDROID)
    52 #define LOG_ERROR(args...) __android_log_print(ANDROID_LOG_ERROR, "Sandbox", ## args)
    53 #elif defined(PR_LOGGING)
    54 static PRLogModuleInfo* gSeccompSandboxLog;
    55 #define LOG_ERROR(args...) PR_LOG(gSeccompSandboxLog, PR_LOG_ERROR, (args))
    56 #else
    57 #define LOG_ERROR(args...)
    58 #endif
    60 /**
    61  * Log JS stack info in the same place as the sandbox violation
    62  * message.  Useful in case the responsible code is JS and all we have
    63  * are logs and a minidump with the C++ stacks (e.g., on TBPL).
    64  */
    65 static void
    66 SandboxLogJSStack(void)
    67 {
    68   if (!NS_IsMainThread()) {
    69     // This might be a worker thread... or it might be a non-JS
    70     // thread, or a non-NSPR thread.  There's isn't a good API for
    71     // dealing with this, yet.
    72     return;
    73   }
    74   nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack();
    75   for (int i = 0; frame != nullptr; ++i) {
    76     nsAutoString fileName, funName;
    77     int32_t lineNumber;
    79     // Don't stop unwinding if an attribute can't be read.
    80     fileName.SetIsVoid(true);
    81     unused << frame->GetFilename(fileName);
    82     lineNumber = 0;
    83     unused << frame->GetLineNumber(&lineNumber);
    84     funName.SetIsVoid(true);
    85     unused << frame->GetName(funName);
    87     if (!funName.IsVoid() || !fileName.IsVoid()) {
    88       LOG_ERROR("JS frame %d: %s %s line %d", i,
    89                 funName.IsVoid() ?
    90                   "(anonymous)" : NS_ConvertUTF16toUTF8(funName).get(),
    91                 fileName.IsVoid() ?
    92                   "(no file)" : NS_ConvertUTF16toUTF8(fileName).get(),
    93                 lineNumber);
    94     }
    96     nsCOMPtr<nsIStackFrame> nextFrame;
    97     nsresult rv = frame->GetCaller(getter_AddRefs(nextFrame));
    98     NS_ENSURE_SUCCESS_VOID(rv);
    99     frame = nextFrame;
   100   }
   101 }
   103 /**
   104  * This is the SIGSYS handler function. It is used to report to the user
   105  * which system call has been denied by Seccomp.
   106  * This function also makes the process exit as denying the system call
   107  * will otherwise generally lead to unexpected behavior from the process,
   108  * since we don't know if all functions will handle such denials gracefully.
   109  *
   110  * @see InstallSyscallReporter() function.
   111  */
   112 #ifdef MOZ_CONTENT_SANDBOX_REPORTER
   113 static void
   114 Reporter(int nr, siginfo_t *info, void *void_context)
   115 {
   116   ucontext_t *ctx = static_cast<ucontext_t*>(void_context);
   117   unsigned long syscall_nr, args[6];
   118   pid_t pid = getpid(), tid = syscall(__NR_gettid);
   120   if (nr != SIGSYS) {
   121     return;
   122   }
   123   if (info->si_code != SYS_SECCOMP) {
   124     return;
   125   }
   126   if (!ctx) {
   127     return;
   128   }
   130   syscall_nr = SECCOMP_SYSCALL(ctx);
   131   args[0] = SECCOMP_PARM1(ctx);
   132   args[1] = SECCOMP_PARM2(ctx);
   133   args[2] = SECCOMP_PARM3(ctx);
   134   args[3] = SECCOMP_PARM4(ctx);
   135   args[4] = SECCOMP_PARM5(ctx);
   136   args[5] = SECCOMP_PARM6(ctx);
   138   LOG_ERROR("seccomp sandbox violation: pid %d, syscall %lu, args %lu %lu %lu"
   139             " %lu %lu %lu.  Killing process.", pid, syscall_nr,
   140             args[0], args[1], args[2], args[3], args[4], args[5]);
   142 #ifdef MOZ_CRASHREPORTER
   143   bool dumped = CrashReporter::WriteMinidumpForSigInfo(nr, info, void_context);
   144   if (!dumped) {
   145     LOG_ERROR("Failed to write minidump");
   146   }
   147 #endif
   149   // Do this last, in case it crashes or deadlocks.
   150   SandboxLogJSStack();
   152   // Try to reraise, so the parent sees that this process crashed.
   153   // (If tgkill is forbidden, then seccomp will raise SIGSYS, which
   154   // also accomplishes that goal.)
   155   signal(SIGSYS, SIG_DFL);
   156   syscall(__NR_tgkill, pid, tid, nr);
   157   _exit(127);
   158 }
   160 /**
   161  * The reporter is called when the process receives a SIGSYS signal.
   162  * The signal is sent by the kernel when Seccomp encounter a system call
   163  * that has not been allowed.
   164  * We register an action for that signal (calling the Reporter function).
   165  *
   166  * This function should not be used in production and thus generally be
   167  * called from debug code. In production, the process is directly killed.
   168  * For this reason, the function is ifdef'd, as there is no reason to
   169  * compile it while unused.
   170  *
   171  * @return 0 on success, -1 on failure.
   172  * @see Reporter() function.
   173  */
   174 static int
   175 InstallSyscallReporter(void)
   176 {
   177   struct sigaction act;
   178   sigset_t mask;
   179   memset(&act, 0, sizeof(act));
   180   sigemptyset(&mask);
   181   sigaddset(&mask, SIGSYS);
   183   act.sa_sigaction = &Reporter;
   184   act.sa_flags = SA_SIGINFO | SA_NODEFER;
   185   if (sigaction(SIGSYS, &act, nullptr) < 0) {
   186     return -1;
   187   }
   188   if (sigemptyset(&mask) ||
   189     sigaddset(&mask, SIGSYS) ||
   190     sigprocmask(SIG_UNBLOCK, &mask, nullptr)) {
   191       return -1;
   192   }
   193   return 0;
   194 }
   195 #endif
   197 /**
   198  * This function installs the syscall filter, a.k.a. seccomp.
   199  * PR_SET_NO_NEW_PRIVS ensures that it is impossible to grant more
   200  * syscalls to the process beyond this point (even after fork()).
   201  * SECCOMP_MODE_FILTER is the "bpf" mode of seccomp which allows
   202  * to pass a bpf program (in our case, it contains a syscall
   203  * whitelist).
   204  *
   205  * @return 0 on success, 1 on failure.
   206  * @see sock_fprog (the seccomp_prog).
   207  */
   208 static int
   209 InstallSyscallFilter(void)
   210 {
   211 #ifdef MOZ_DMD
   212   char* e = PR_GetEnv("DMD");
   213   if (e && strcmp(e, "") != 0 && strcmp(e, "0") != 0) {
   214     LOG_ERROR("SANDBOX DISABLED FOR DMD!  See bug 956961.");
   215     // Must treat this as "failure" in order to prevent infinite loop;
   216     // cf. the PR_GET_SECCOMP check below.
   217     return 1;
   218   }
   219 #endif
   220   if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
   221     return 1;
   222   }
   224   const sock_fprog *filter = GetSandboxFilter();
   226   if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)filter, 0, 0)) {
   227     return 1;
   228   }
   229   return 0;
   230 }
   232 // Use signals for permissions that need to be set per-thread.
   233 // The communication channel from the signal handler back to the main thread.
   234 static mozilla::Atomic<int> sSetSandboxDone;
   235 // about:memory has the first 3 RT signals.  (We should allocate
   236 // signals centrally instead of hard-coding them like this.)
   237 static const int sSetSandboxSignum = SIGRTMIN + 3;
   239 static bool
   240 SetThreadSandbox()
   241 {
   242   bool didAnything = false;
   244   if (PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX") == nullptr &&
   245       prctl(PR_GET_SECCOMP, 0, 0, 0, 0) == 0) {
   246     if (InstallSyscallFilter() == 0) {
   247       didAnything = true;
   248     }
   249     /*
   250      * Bug 880797: when all B2G devices are required to support
   251      * seccomp-bpf, this should exit/crash if InstallSyscallFilter
   252      * returns nonzero (ifdef MOZ_WIDGET_GONK).
   253      */
   254   }
   255   return didAnything;
   256 }
   258 static void
   259 SetThreadSandboxHandler(int signum)
   260 {
   261   // The non-zero number sent back to the main thread indicates
   262   // whether action was taken.
   263   if (SetThreadSandbox()) {
   264     sSetSandboxDone = 2;
   265   } else {
   266     sSetSandboxDone = 1;
   267   }
   268   // Wake up the main thread.  See the FUTEX_WAIT call, below, for an
   269   // explanation.
   270   syscall(__NR_futex, reinterpret_cast<int*>(&sSetSandboxDone),
   271           FUTEX_WAKE, 1);
   272 }
   274 static void
   275 BroadcastSetThreadSandbox()
   276 {
   277   pid_t pid, tid;
   278   DIR *taskdp;
   279   struct dirent *de;
   281   static_assert(sizeof(mozilla::Atomic<int>) == sizeof(int),
   282                 "mozilla::Atomic<int> isn't represented by an int");
   283   MOZ_ASSERT(NS_IsMainThread());
   284   pid = getpid();
   285   taskdp = opendir("/proc/self/task");
   286   if (taskdp == nullptr) {
   287     LOG_ERROR("opendir /proc/self/task: %s\n", strerror(errno));
   288     MOZ_CRASH();
   289   }
   290   if (signal(sSetSandboxSignum, SetThreadSandboxHandler) != SIG_DFL) {
   291     LOG_ERROR("signal %d in use!\n", sSetSandboxSignum);
   292     MOZ_CRASH();
   293   }
   295   // In case this races with a not-yet-deprivileged thread cloning
   296   // itself, repeat iterating over all threads until we find none
   297   // that are still privileged.
   298   bool sandboxProgress;
   299   do {
   300     sandboxProgress = false;
   301     // For each thread...
   302     while ((de = readdir(taskdp))) {
   303       char *endptr;
   304       tid = strtol(de->d_name, &endptr, 10);
   305       if (*endptr != '\0' || tid <= 0) {
   306         // Not a task ID.
   307         continue;
   308       }
   309       if (tid == pid) {
   310         // Drop the main thread's privileges last, below, so
   311         // we can continue to signal other threads.
   312         continue;
   313       }
   314       // Reset the futex cell and signal.
   315       sSetSandboxDone = 0;
   316       if (syscall(__NR_tgkill, pid, tid, sSetSandboxSignum) != 0) {
   317         if (errno == ESRCH) {
   318           LOG_ERROR("Thread %d unexpectedly exited.", tid);
   319           // Rescan threads, in case it forked before exiting.
   320           sandboxProgress = true;
   321           continue;
   322         }
   323         LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno));
   324         MOZ_CRASH();
   325       }
   326       // It's unlikely, but if the thread somehow manages to exit
   327       // after receiving the signal but before entering the signal
   328       // handler, we need to avoid blocking forever.
   329       //
   330       // Using futex directly lets the signal handler send the wakeup
   331       // from an async signal handler (pthread mutex/condvar calls
   332       // aren't allowed), and to use a relative timeout that isn't
   333       // affected by changes to the system clock (not possible with
   334       // POSIX semaphores).
   335       //
   336       // If a thread doesn't respond within a reasonable amount of
   337       // time, but still exists, we crash -- the alternative is either
   338       // blocking forever or silently losing security, and it
   339       // shouldn't actually happen.
   340       static const int crashDelay = 10; // seconds
   341       struct timespec timeLimit;
   342       clock_gettime(CLOCK_MONOTONIC, &timeLimit);
   343       timeLimit.tv_sec += crashDelay;
   344       while (true) {
   345         static const struct timespec futexTimeout = { 0, 10*1000*1000 }; // 10ms
   346         // Atomically: if sSetSandboxDone == 0, then sleep.
   347         if (syscall(__NR_futex, reinterpret_cast<int*>(&sSetSandboxDone),
   348                   FUTEX_WAIT, 0, &futexTimeout) != 0) {
   349           if (errno != EWOULDBLOCK && errno != ETIMEDOUT && errno != EINTR) {
   350             LOG_ERROR("FUTEX_WAIT: %s\n", strerror(errno));
   351             MOZ_CRASH();
   352           }
   353         }
   354         // Did the handler finish?
   355         if (sSetSandboxDone > 0) {
   356           if (sSetSandboxDone == 2) {
   357             sandboxProgress = true;
   358           }
   359           break;
   360         }
   361         // Has the thread ceased to exist?
   362         if (syscall(__NR_tgkill, pid, tid, 0) != 0) {
   363           if (errno == ESRCH) {
   364             LOG_ERROR("Thread %d unexpectedly exited.", tid);
   365           }
   366           // Rescan threads, in case it forked before exiting.
   367           // Also, if it somehow failed in a way that wasn't ESRCH,
   368           // and still exists, that will be handled on the next pass.
   369           sandboxProgress = true;
   370           break;
   371         }
   372         struct timespec now;
   373         clock_gettime(CLOCK_MONOTONIC, &now);
   374         if (now.tv_sec > timeLimit.tv_nsec ||
   375             (now.tv_sec == timeLimit.tv_nsec &&
   376              now.tv_nsec > timeLimit.tv_nsec)) {
   377           LOG_ERROR("Thread %d unresponsive for %d seconds.  Killing process.",
   378                     tid, crashDelay);
   379           MOZ_CRASH();
   380         }
   381       }
   382     }
   383     rewinddir(taskdp);
   384   } while (sandboxProgress);
   385   unused << signal(sSetSandboxSignum, SIG_DFL);
   386   unused << closedir(taskdp);
   387   // And now, deprivilege the main thread:
   388   SetThreadSandbox();
   389 }
   391 // This function can overapproximate (i.e., return true even if
   392 // sandboxing isn't supported, but not the reverse).  See bug 993145.
   393 static bool
   394 IsSandboxingSupported(void)
   395 {
   396   return prctl(PR_GET_SECCOMP) != -1;
   397 }
   399 /**
   400  * Starts the seccomp sandbox for this process and sets user/group-based privileges.
   401  * Should be called only once, and before any potentially harmful content is loaded.
   402  *
   403  * Should normally make the process exit on failure.
   404 */
   405 void
   406 SetCurrentProcessSandbox()
   407 {
   408 #if !defined(ANDROID) && defined(PR_LOGGING)
   409   if (!gSeccompSandboxLog) {
   410     gSeccompSandboxLog = PR_NewLogModule("SeccompSandbox");
   411   }
   412   PR_ASSERT(gSeccompSandboxLog);
   413 #endif
   415 #if defined(MOZ_CONTENT_SANDBOX_REPORTER)
   416   if (InstallSyscallReporter()) {
   417     LOG_ERROR("install_syscall_reporter() failed\n");
   418   }
   419 #endif
   421   if (IsSandboxingSupported()) {
   422     BroadcastSetThreadSandbox();
   423   }
   424 }
   426 } // namespace mozilla

mercurial