Bug 1393287 - Intercept sigaction() to fix signal masks for sandboxing. r?gcp draft
authorJed Davis <jld@xlerb.net>
Fri, 08 Dec 2017 17:31:07 -0700
changeset 710297 78434b5c41fd9ef810144e83fc64a3a74090462b
parent 710280 5f52c2488a831edbc33fa0bc6003ed4df9a62732
child 743568 1b0e67c8d34b0efae53338fb7e6dd6eb85aa2da2
push id92807
push userbmo:jld@mozilla.com
push dateSat, 09 Dec 2017 07:22:05 +0000
reviewersgcp
bugs1393287
milestone59.0a1
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
security/sandbox/linux/Sandbox.cpp
security/sandbox/linux/SandboxHooks.cpp
--- 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)
 {