security/sandbox/linux/Sandbox.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial