security/sandbox/linux/Sandbox.cpp

branch
TOR_BUG_9701
changeset 3
141e0f1194b1
equal deleted inserted replaced
-1:000000000000 0:64be002f5ea8
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/. */
6
7 #include "mozilla/Sandbox.h"
8
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>
22
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"
29
30 #ifdef MOZ_CRASHREPORTER
31 #include "nsExceptionHandler.h"
32 #endif
33
34 #if defined(ANDROID)
35 #include "android_ucontext.h"
36 #include <android/log.h>
37 #endif
38
39 #if defined(MOZ_CONTENT_SANDBOX)
40 #include "linux_seccomp.h"
41 #include "SandboxFilter.h"
42 #endif
43
44 #ifdef MOZ_LOGGING
45 #define FORCE_PR_LOG 1
46 #endif
47 #include "prlog.h"
48 #include "prenv.h"
49
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
59
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;
78
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);
86
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 }
95
96 nsCOMPtr<nsIStackFrame> nextFrame;
97 nsresult rv = frame->GetCaller(getter_AddRefs(nextFrame));
98 NS_ENSURE_SUCCESS_VOID(rv);
99 frame = nextFrame;
100 }
101 }
102
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);
119
120 if (nr != SIGSYS) {
121 return;
122 }
123 if (info->si_code != SYS_SECCOMP) {
124 return;
125 }
126 if (!ctx) {
127 return;
128 }
129
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);
137
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]);
141
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
148
149 // Do this last, in case it crashes or deadlocks.
150 SandboxLogJSStack();
151
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 }
159
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);
182
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
196
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 }
223
224 const sock_fprog *filter = GetSandboxFilter();
225
226 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, (unsigned long)filter, 0, 0)) {
227 return 1;
228 }
229 return 0;
230 }
231
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;
238
239 static bool
240 SetThreadSandbox()
241 {
242 bool didAnything = false;
243
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 }
257
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 }
273
274 static void
275 BroadcastSetThreadSandbox()
276 {
277 pid_t pid, tid;
278 DIR *taskdp;
279 struct dirent *de;
280
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 }
294
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 }
390
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 }
398
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
414
415 #if defined(MOZ_CONTENT_SANDBOX_REPORTER)
416 if (InstallSyscallReporter()) {
417 LOG_ERROR("install_syscall_reporter() failed\n");
418 }
419 #endif
420
421 if (IsSandboxingSupported()) {
422 BroadcastSetThreadSandbox();
423 }
424 }
425
426 } // namespace mozilla
427

mercurial