Bug 1393287 - Intercept sigaction() to fix signal masks for sandboxing. r?gcp
Also changes gSeccompTsyncBroadcastSignum to an atomic, in case these
wrappers race with starting the sandbox, and optimizes the wrappers
slightly by avoiding unnecessary copying of signal sets or sigactions.
Tested by manaully LD_PRELOADing libmozsandbox in the parent process,
because it already has a few signal handlers with block-by-default
masks.
MozReview-Commit-ID: CiHsA6rOCrQ
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -73,17 +73,17 @@ typedef struct {
} __sanitizer_sandbox_arguments;
MOZ_IMPORT_API void
__sanitizer_sandbox_on_notify(__sanitizer_sandbox_arguments *args);
} // extern "C"
#endif // MOZ_ASAN
// Signal number used to enable seccomp on each thread.
-int gSeccompTsyncBroadcastSignum = 0;
+mozilla::Atomic<int> gSeccompTsyncBroadcastSignum(0);
namespace mozilla {
static bool gSandboxCrashOnError = false;
// This is initialized by SandboxSetCrashFunc().
SandboxCrashFunc gSandboxCrashFunc;
@@ -331,37 +331,38 @@ BroadcastSetThreadSandbox(const sock_fpr
}
EnterChroot();
// 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;
+ const int tsyncSignum = gSeccompTsyncBroadcastSignum;
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 == myTid) {
// Drop this thread's privileges last, below, so we can
// continue to signal other threads.
continue;
}
- MOZ_RELEASE_ASSERT(gSeccompTsyncBroadcastSignum != 0);
+ MOZ_RELEASE_ASSERT(tsyncSignum != 0);
// Reset the futex cell and signal.
gSetSandboxDone = 0;
- if (syscall(__NR_tgkill, pid, tid, gSeccompTsyncBroadcastSignum) != 0) {
+ if (syscall(__NR_tgkill, pid, tid, tsyncSignum) != 0) {
if (errno == ESRCH) {
SANDBOX_LOG_ERROR("Thread %d unexpectedly exited.", tid);
// Rescan threads, in case it forked before exiting.
sandboxProgress = true;
continue;
}
SANDBOX_LOG_ERROR("tgkill(%d,%d): %s\n", pid, tid, strerror(errno));
MOZ_CRASH();
@@ -423,24 +424,24 @@ BroadcastSetThreadSandbox(const sock_fpr
MOZ_CRASH();
}
}
}
rewinddir(taskdp);
} while (sandboxProgress);
void (*oldHandler)(int);
- oldHandler = signal(gSeccompTsyncBroadcastSignum, SIG_DFL);
- gSeccompTsyncBroadcastSignum = 0;
+ oldHandler = signal(tsyncSignum, SIG_DFL);
if (oldHandler != SetThreadSandboxHandler) {
// See the comment on FindFreeSignalNumber about race conditions.
SANDBOX_LOG_ERROR("handler for signal %d was changed to %p!",
- gSeccompTsyncBroadcastSignum, oldHandler);
+ tsyncSignum, oldHandler);
MOZ_CRASH();
}
+ gSeccompTsyncBroadcastSignum = 0;
Unused << closedir(taskdp);
// And now, deprivilege the main thread:
SetThreadSandbox();
gSetSandboxFilter = nullptr;
}
static void
ApplySandboxWithTSync(sock_fprog* aFilter)
@@ -580,28 +581,29 @@ SandboxEarlyInit(GeckoProcessType aType)
default:
// Other cases intentionally left blank.
break;
}
// If TSYNC is not supported, set up signal handler
// used to enable seccomp on each thread.
if (!info.Test(SandboxInfo::kHasSeccompTSync)) {
- gSeccompTsyncBroadcastSignum = FindFreeSignalNumber();
- if (gSeccompTsyncBroadcastSignum == 0) {
+ const int tsyncSignum = FindFreeSignalNumber();
+ if (tsyncSignum == 0) {
SANDBOX_LOG_ERROR("No available signal numbers!");
MOZ_CRASH();
}
+ gSeccompTsyncBroadcastSignum = tsyncSignum;
void (*oldHandler)(int);
- oldHandler = signal(gSeccompTsyncBroadcastSignum, SetThreadSandboxHandler);
+ oldHandler = signal(tsyncSignum, SetThreadSandboxHandler);
if (oldHandler != SIG_DFL) {
// See the comment on FindFreeSignalNumber about race conditions.
SANDBOX_LOG_ERROR("signal %d in use by handler %p!\n",
- gSeccompTsyncBroadcastSignum, oldHandler);
+ tsyncSignum, oldHandler);
MOZ_CRASH();
}
}
// If there's nothing to do, then we're done.
if (!canChroot && !canUnshareNet && !canUnshareIPC) {
return;
}
--- a/security/sandbox/linux/SandboxHooks.cpp
+++ b/security/sandbox/linux/SandboxHooks.cpp
@@ -1,61 +1,77 @@
/* -*- 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/Atomics.h"
#include "mozilla/Types.h"
#include <dlfcn.h>
#include <signal.h>
#include <errno.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
// Signal number used to enable seccomp on each thread.
-extern int gSeccompTsyncBroadcastSignum;
+extern mozilla::Atomic<int> gSeccompTsyncBroadcastSignum;
+
+static bool
+SigSetNeedsFixup(const sigset_t* aSet)
+{
+ int tsyncSignum = gSeccompTsyncBroadcastSignum;
+
+ return aSet != nullptr &&
+ (sigismember(aSet, SIGSYS) ||
+ (tsyncSignum != 0 &&
+ sigismember(aSet, tsyncSignum)));
+}
+
+static void
+SigSetFixup(sigset_t* aSet)
+{
+ int tsyncSignum = gSeccompTsyncBroadcastSignum;
+ int rv = sigdelset(aSet, SIGSYS);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ if (tsyncSignum != 0) {
+ rv = sigdelset(aSet, tsyncSignum);
+ MOZ_RELEASE_ASSERT(rv == 0);
+ }
+}
// This file defines a hook for sigprocmask() and pthread_sigmask().
// Bug 1176099: some threads block SIGSYS signal which breaks our seccomp-bpf
// sandbox. To avoid this, we intercept the call and remove SIGSYS.
//
// ENOSYS indicates an error within the hook function itself.
-static int HandleSigset(int (*aRealFunc)(int, const sigset_t*, sigset_t*),
- int aHow, const sigset_t* aSet,
- sigset_t* aOldSet, bool aUseErrno)
+static int
+HandleSigset(int (*aRealFunc)(int, const sigset_t*, sigset_t*),
+ int aHow, const sigset_t* aSet,
+ sigset_t* aOldSet, bool aUseErrno)
{
if (!aRealFunc) {
if (aUseErrno) {
errno = ENOSYS;
return -1;
}
return ENOSYS;
}
// Avoid unnecessary work
- if (aSet == nullptr || aHow == SIG_UNBLOCK) {
+ if (aHow == SIG_UNBLOCK || !SigSetNeedsFixup(aSet)) {
return aRealFunc(aHow, aSet, aOldSet);
}
sigset_t newSet = *aSet;
- if (sigdelset(&newSet, SIGSYS) != 0 ||
- (gSeccompTsyncBroadcastSignum &&
- sigdelset(&newSet, gSeccompTsyncBroadcastSignum) != 0)) {
- if (aUseErrno) {
- errno = ENOSYS;
- return -1;
- }
-
- return ENOSYS;
- }
-
+ SigSetFixup(&newSet);
return aRealFunc(aHow, &newSet, aOldSet);
}
extern "C" MOZ_EXPORT int
sigprocmask(int how, const sigset_t* set, sigset_t* oldset)
{
static auto sRealFunc = (int (*)(int, const sigset_t*, sigset_t*))
dlsym(RTLD_NEXT, "sigprocmask");
@@ -68,16 +84,37 @@ pthread_sigmask(int how, const sigset_t*
{
static auto sRealFunc = (int (*)(int, const sigset_t*, sigset_t*))
dlsym(RTLD_NEXT, "pthread_sigmask");
return HandleSigset(sRealFunc, how, set, oldset, false);
}
extern "C" MOZ_EXPORT int
+sigaction(int signum, const struct sigaction* act, struct sigaction* oldact)
+{
+ static auto sRealFunc =
+ (int (*)(int, const struct sigaction*, struct sigaction*))
+ dlsym(RTLD_NEXT, "sigaction");
+
+ if (!sRealFunc) {
+ errno = ENOSYS;
+ return -1;
+ }
+
+ if (act == nullptr || !SigSetNeedsFixup(&act->sa_mask)) {
+ return sRealFunc(signum, act, oldact);
+ }
+
+ struct sigaction newact = *act;
+ SigSetFixup(&newact.sa_mask);
+ return sRealFunc(signum, &newact, oldact);
+}
+
+extern "C" MOZ_EXPORT int
inotify_init(void)
{
return inotify_init1(0);
}
extern "C" MOZ_EXPORT int
inotify_init1(int flags)
{