Bug 1286865 - Step 1: Gather syscall info from SIGSYS handlers into the parent process. r=gcp
authorJed Davis <jld@mozilla.com>
Mon, 30 Jan 2017 18:49:53 -0700
changeset 372978 f73368ed36cf12bf18f7d66f370d5cd6b8a5e8db
parent 372977 7781de08a1c6d84a92e9d54a78ac9f54f8c4c240
child 372979 30d3b74aaa013e89e7189d9544f42357b8b2ab44
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp
bugs1286865
milestone54.0a1
Bug 1286865 - Step 1: Gather syscall info from SIGSYS handlers into the parent process. r=gcp MozReview-Commit-ID: 8GfFo4xso65
ipc/glue/GeckoChildProcessHost.cpp
security/sandbox/linux/Sandbox.cpp
security/sandbox/linux/SandboxFilter.cpp
security/sandbox/linux/SandboxReporterClient.cpp
security/sandbox/linux/SandboxReporterClient.h
security/sandbox/linux/moz.build
security/sandbox/linux/reporter/SandboxReporter.cpp
security/sandbox/linux/reporter/SandboxReporter.h
security/sandbox/linux/reporter/SandboxReporterCommon.h
security/sandbox/linux/reporter/moz.build
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -46,16 +46,20 @@
 
 #if defined(MOZ_SANDBOX)
 #include "mozilla/Preferences.h"
 #include "mozilla/sandboxing/sandboxLogging.h"
 #include "nsDirectoryServiceUtils.h"
 #endif
 #endif
 
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+#include "mozilla/SandboxReporter.h"
+#endif
+
 #include "nsTArray.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsNativeCharsetUtils.h"
 #include "nscore.h" // for NS_FREE_PERMANENT_DATA
 
 using mozilla::MonitorAutoLock;
 using mozilla::ipc::GeckoChildProcessHost;
@@ -907,16 +911,25 @@ GeckoChildProcessHost::PerformAsyncLaunc
     // "false" == crash reporting disabled
     childArgv.push_back("false");
   }
 #  elif defined(MOZ_WIDGET_COCOA)
   childArgv.push_back(CrashReporter::GetChildNotificationPipe());
 #  endif  // OS_LINUX
 #endif
 
+#if defined(XP_LINUX) && defined(MOZ_SANDBOX)
+  {
+    int srcFd, dstFd;
+    SandboxReporter::Singleton()
+      ->GetClientFileDescriptorMapping(&srcFd, &dstFd);
+    mFileMap.push_back(std::make_pair(srcFd, dstFd));
+  }
+#endif
+
 #ifdef MOZ_WIDGET_COCOA
   // Add a mach port to the command line so the child can communicate its
   // 'task_t' back to the parent.
   //
   // Put a random number into the channel name, so that a compromised renderer
   // can't pretend being the child that's forked off.
   std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d",
                                                   base::RandInt(0, std::numeric_limits<int>::max()));
--- a/security/sandbox/linux/Sandbox.cpp
+++ b/security/sandbox/linux/Sandbox.cpp
@@ -8,16 +8,17 @@
 
 #include "LinuxCapabilities.h"
 #include "LinuxSched.h"
 #include "SandboxBrokerClient.h"
 #include "SandboxChroot.h"
 #include "SandboxFilter.h"
 #include "SandboxInternal.h"
 #include "SandboxLogging.h"
+#include "SandboxReporterClient.h"
 #include "SandboxUtil.h"
 
 #include <dirent.h>
 #include <errno.h>
 #include <fcntl.h>
 #include <linux/futex.h>
 #include <pthread.h>
 #include <signal.h>
@@ -80,16 +81,17 @@ SandboxCrashFunc gSandboxCrashFunc;
 
 #ifdef MOZ_GMP_SANDBOX
 // For media plugins, we can start the sandbox before we dlopen the
 // module, so we have to pre-open the file and simulate the sandboxed
 // open().
 static SandboxOpenedFile gMediaPluginFile;
 #endif
 
+static Maybe<SandboxReporterClient> gSandboxReporterClient;
 static UniquePtr<SandboxChroot> gChrootHelper;
 static void (*gChromiumSigSysHandler)(int, siginfo_t*, void*);
 
 // Test whether a ucontext, interpreted as the state after a syscall,
 // indicates the given error.  See also sandbox::Syscall::PutValueInUcontext.
 static bool
 ContextIsError(const ucontext_t *aContext, int aError)
 {
@@ -132,37 +134,30 @@ SigSysHandler(int nr, siginfo_t *info, v
   // which will overwrite one or more registers with the return value.
   ucontext_t savedCtx = *ctx;
 
   gChromiumSigSysHandler(nr, info, ctx);
   if (!ContextIsError(ctx, ENOSYS)) {
     return;
   }
 
-  pid_t pid = getpid();
-  unsigned long syscall_nr = SECCOMP_SYSCALL(&savedCtx);
-  unsigned long args[6];
-  args[0] = SECCOMP_PARM1(&savedCtx);
-  args[1] = SECCOMP_PARM2(&savedCtx);
-  args[2] = SECCOMP_PARM3(&savedCtx);
-  args[3] = SECCOMP_PARM4(&savedCtx);
-  args[4] = SECCOMP_PARM5(&savedCtx);
-  args[5] = SECCOMP_PARM6(&savedCtx);
+  SandboxReport report = gSandboxReporterClient->MakeReportAndSend(&savedCtx);
 
   // TODO, someday when this is enabled on MIPS: include the two extra
   // args in the error message.
-  SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, syscall %d,"
+  SANDBOX_LOG_ERROR("seccomp sandbox violation: pid %d, tid %d, syscall %d,"
                     " args %d %d %d %d %d %d.%s",
-                    pid, syscall_nr,
-                    args[0], args[1], args[2], args[3], args[4], args[5],
+                    report.mPid, report.mTid, report.mSyscall,
+                    report.mArgs[0], report.mArgs[1], report.mArgs[2],
+                    report.mArgs[3], report.mArgs[4], report.mArgs[5],
                     gSandboxCrashOnError ? "  Killing process." : "");
 
   if (gSandboxCrashOnError) {
     // Bug 1017393: record syscall number somewhere useful.
-    info->si_addr = reinterpret_cast<void*>(syscall_nr);
+    info->si_addr = reinterpret_cast<void*>(report.mSyscall);
 
     gSandboxCrashFunc(nr, info, &savedCtx);
     _exit(127);
   }
 }
 
 /**
  * This function installs the SIGSYS handler.  This is slightly
@@ -459,16 +454,17 @@ ApplySandboxWithTSync(sock_fprog* aFilte
   }
 }
 
 // Common code for sandbox startup.
 static void
 SetCurrentProcessSandbox(UniquePtr<sandbox::bpf_dsl::Policy> aPolicy)
 {
   MOZ_ASSERT(gSandboxCrashFunc);
+  MOZ_RELEASE_ASSERT(gSandboxReporterClient.isSome());
 
   // Note: PolicyCompiler borrows the policy and registry for its
   // lifetime, but does not take ownership of them.
   sandbox::bpf_dsl::PolicyCompiler compiler(aPolicy.get(),
                                             sandbox::Trap::Registry());
   sandbox::CodeGen::Program program = compiler.Compile();
   if (SandboxInfo::Get().Test(SandboxInfo::kVerbose)) {
     sandbox::bpf_dsl::DumpBPF::PrintProgram(program);
@@ -658,16 +654,18 @@ SetContentProcessSandbox(int aBrokerFd, 
 {
   if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForContent)) {
     if (aBrokerFd >= 0) {
       close(aBrokerFd);
     }
     return false;
   }
 
+  gSandboxReporterClient.emplace(SandboxReport::ProcType::CONTENT);
+
   // This needs to live until the process exits.
   static Maybe<SandboxBrokerClient> sBroker;
   if (aBrokerFd >= 0) {
     sBroker.emplace(aBrokerFd);
   }
 
   SetCurrentProcessSandbox(GetContentSandboxPolicy(sBroker.ptrOr(nullptr),
                                                    aSyscallWhitelist));
@@ -689,16 +687,18 @@ SetContentProcessSandbox(int aBrokerFd, 
 */
 void
 SetMediaPluginSandbox(const char *aFilePath)
 {
   if (!SandboxInfo::Get().Test(SandboxInfo::kEnabledForMedia)) {
     return;
   }
 
+  gSandboxReporterClient.emplace(SandboxReport::ProcType::MEDIA_PLUGIN);
+
   MOZ_ASSERT(!gMediaPluginFile.mPath);
   if (aFilePath) {
     gMediaPluginFile.mPath = strdup(aFilePath);
     gMediaPluginFile.mFd = open(aFilePath, O_RDONLY | O_CLOEXEC);
     if (gMediaPluginFile.mFd == -1) {
       SANDBOX_LOG_ERROR("failed to open plugin file %s: %s",
                         aFilePath, strerror(errno));
       MOZ_CRASH();
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -164,16 +164,17 @@ public:
 
   virtual ResultExpr EvaluateSyscall(int sysno) const override {
     switch (sysno) {
       // Timekeeping
     case __NR_clock_gettime: {
       Arg<clockid_t> clk_id(0);
       return If(clk_id == CLOCK_MONOTONIC, Allow())
 #ifdef CLOCK_MONOTONIC_COARSE
+        // Used by SandboxReporter, among other things.
         .ElseIf(clk_id == CLOCK_MONOTONIC_COARSE, Allow())
 #endif
         .ElseIf(clk_id == CLOCK_PROCESS_CPUTIME_ID, Allow())
         .ElseIf(clk_id == CLOCK_REALTIME, Allow())
 #ifdef CLOCK_REALTIME_COARSE
         .ElseIf(clk_id == CLOCK_REALTIME_COARSE, Allow())
 #endif
         .ElseIf(clk_id == CLOCK_THREAD_CPUTIME_ID, Allow())
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/SandboxReporterClient.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "SandboxReporterClient.h"
+#include "SandboxLogging.h"
+
+#include <errno.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <time.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+#include "sandbox/linux/bpf_dsl/seccomp_macros.h"
+#ifdef ANDROID
+#include "sandbox/linux/system_headers/linux_ucontext.h"
+#else
+#include <ucontext.h>
+#endif
+
+namespace mozilla {
+
+SandboxReporterClient::SandboxReporterClient(SandboxReport::ProcType aProcType,
+					     int aFd)
+  : mProcType(aProcType)
+  , mFd(aFd)
+{
+  // Unfortunately, there isn't a good way to check that the fd is a
+  // socket connected to the right thing without attempting some kind
+  // of in-band handshake.  However, the crash reporter (which also
+  // uses a "magic number" fd) doesn't do any kind of checking either,
+  // so it's probably okay to skip it here.
+}
+
+SandboxReport
+SandboxReporterClient::MakeReport(const void* aContext)
+{
+  SandboxReport report;
+  const auto ctx = static_cast<const ucontext_t*>(aContext);
+
+  // Zero the entire struct; some memory safety analyses care about
+  // sending uninitialized alignment padding to another process.
+  PodZero(&report);
+
+  clock_gettime(CLOCK_MONOTONIC_COARSE, &report.mTime);
+  report.mPid = getpid();
+  report.mTid = syscall(__NR_gettid);
+  report.mProcType = mProcType;
+  report.mSyscall = SECCOMP_SYSCALL(ctx);
+  report.mArgs[0] = SECCOMP_PARM1(ctx);
+  report.mArgs[1] = SECCOMP_PARM2(ctx);
+  report.mArgs[2] = SECCOMP_PARM3(ctx);
+  report.mArgs[3] = SECCOMP_PARM4(ctx);
+  report.mArgs[4] = SECCOMP_PARM5(ctx);
+  report.mArgs[5] = SECCOMP_PARM6(ctx);
+  // Named Return Value Optimization allows the compiler to optimize
+  // out the copy here (and the one in MakeReportAndSend).
+  return report;
+}
+
+void
+SandboxReporterClient::SendReport(const SandboxReport& aReport)
+{
+  // The "common" seccomp-bpf policy allows sendmsg but not send(to),
+  // so just use sendmsg even though send would suffice for this.
+  struct iovec iov;
+  struct msghdr msg;
+
+  iov.iov_base = const_cast<void*>(static_cast<const void*>(&aReport));
+  iov.iov_len = sizeof(SandboxReport);
+  PodZero(&msg);
+  msg.msg_iov = &iov;
+  msg.msg_iovlen = 1;
+
+  const auto sent = sendmsg(mFd, &msg, MSG_NOSIGNAL);
+
+  if (sent != sizeof(SandboxReport)) {
+    MOZ_DIAGNOSTIC_ASSERT(sent == -1);
+    SANDBOX_LOG_ERROR("Failed to report rejected syscall: %s",
+		      strerror(errno));
+  }
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/SandboxReporterClient.h
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+
+#ifndef mozilla_SandboxReporterClient_h
+#define mozilla_SandboxReporterClient_h
+
+#include "reporter/SandboxReporterCommon.h"
+
+namespace mozilla {
+
+// This class is instantiated in child processes in Sandbox.cpp to
+// send reports from the SIGSYS handler to the SandboxReporter
+// instance in the parent.
+class SandboxReporterClient {
+public:
+  // Note: this does not take ownership of the file descriptor; if
+  // it's not kSandboxReporterFileDesc (e.g., for unit testing), the
+  // caller will need to close it to avoid leaks.
+  explicit SandboxReporterClient(SandboxReport::ProcType aProcType,
+                                 int aFd = kSandboxReporterFileDesc);
+
+  // Constructs a report from a signal context (the ucontext_t* passed
+  // as void* to an sa_sigaction handler); uses the caller's pid and tid.
+  SandboxReport MakeReport(const void* aContext);
+
+  void SendReport(const SandboxReport& aReport);
+
+  SandboxReport MakeReportAndSend(const void* aContext) {
+    SandboxReport report = MakeReport(aContext);
+    SendReport(report);
+    return report;
+  }
+private:
+  SandboxReport::ProcType mProcType;
+  int mFd;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporterClient_h
--- a/security/sandbox/linux/moz.build
+++ b/security/sandbox/linux/moz.build
@@ -62,16 +62,17 @@ SOURCES += [
     'Sandbox.cpp',
     'SandboxBrokerClient.cpp',
     'SandboxChroot.cpp',
     'SandboxFilter.cpp',
     'SandboxFilterUtil.cpp',
     'SandboxHooks.cpp',
     'SandboxInfo.cpp',
     'SandboxLogging.cpp',
+    'SandboxReporterClient.cpp',
     'SandboxUtil.cpp',
 ]
 
 # This copy of SafeSPrintf doesn't need to avoid the Chromium logging
 # dependency like the one in libxul does, but this way the behavior is
 # consistent.  See also the comment in SandboxLogging.h.
 SOURCES['../chromium/base/strings/safe_sprintf.cc'].flags += ['-DNDEBUG']
 
@@ -104,13 +105,14 @@ if CONFIG['OS_TARGET'] != 'Android':
     # Needed for clock_gettime with glibc < 2.17:
     OS_LIBS += [
         'rt',
     ]
 
 DIRS += [
     'broker',
     'glue',
+    'reporter',
 ]
 
 TEST_DIRS += [
     'gtest',
 ]
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporter.cpp
@@ -0,0 +1,177 @@
+/* -*- 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 "SandboxReporter.h"
+#include "SandboxLogging.h"
+
+#include <algorithm>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/PodOperations.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+StaticAutoPtr<SandboxReporter> SandboxReporter::sSingleton;
+
+SandboxReporter::SandboxReporter()
+  : mClientFd(-1)
+  , mServerFd(-1)
+  , mMutex("SandboxReporter")
+  , mBuffer(MakeUnique<SandboxReport[]>(kSandboxReporterBufferSize))
+  , mCount(0)
+{
+}
+
+bool
+SandboxReporter::Init()
+{
+  int fds[2];
+
+  if (0 != socketpair(AF_UNIX, SOCK_SEQPACKET, 0, fds)) {
+    SANDBOX_LOG_ERROR("SandboxReporter: socketpair failed: %s",
+		      strerror(errno));
+    return false;
+  }
+  mClientFd = fds[0];
+  mServerFd = fds[1];
+
+  if (!PlatformThread::Create(0, this, &mThread)) {
+    SANDBOX_LOG_ERROR("SandboxReporter: thread creation failed: %s",
+                      strerror(errno));
+    close(mClientFd);
+    close(mServerFd);
+    mClientFd = mServerFd = -1;
+    return false;
+  }
+
+  return true;
+}
+
+SandboxReporter::~SandboxReporter()
+{
+  if (mServerFd < 0) {
+    return;
+  }
+  shutdown(mServerFd, SHUT_RD);
+  PlatformThread::Join(mThread);
+  close(mServerFd);
+  close(mClientFd);
+}
+
+/* static */ SandboxReporter*
+SandboxReporter::Singleton()
+{
+  static StaticMutex sMutex;
+  StaticMutexAutoLock lock(sMutex);
+
+  if (sSingleton == nullptr) {
+    sSingleton = new SandboxReporter();
+    if (!sSingleton->Init()) {
+      // If socketpair or thread creation failed, trying to continue
+      // with child process creation is unlikely to succeed; crash
+      // instead of trying to handle that case.
+      MOZ_CRASH("SandboxRepoter::Singleton: initialization failed");
+    }
+    // ClearOnShutdown must be called on the main thread and will
+    // destroy the object on the main thread.  That *should* be safe;
+    // the destructor will shut down the reporter's socket reader
+    // thread before freeing anything, IPC should already be shut down
+    // by that point (so it won't race by calling Singleton()), all
+    // non-main XPCOM threads will also be shut down, and currently
+    // the only other user is the main-thread-only Troubleshoot.jsm.
+    NS_DispatchToMainThread(NS_NewRunnableFunction([] {
+      ClearOnShutdown(&sSingleton);
+    }));
+  }
+  return sSingleton.get();
+}
+
+void
+SandboxReporter::GetClientFileDescriptorMapping(int* aSrcFd, int* aDstFd) const
+{
+  MOZ_ASSERT(mClientFd >= 0);
+  *aSrcFd = mClientFd;
+  *aDstFd = kSandboxReporterFileDesc;
+}
+
+void
+SandboxReporter::AddOne(const SandboxReport& aReport)
+{
+  // TODO: send a copy to Telemetry
+  MutexAutoLock lock(mMutex);
+  mBuffer[mCount % kSandboxReporterBufferSize] = aReport;
+  ++mCount;
+}
+
+void
+SandboxReporter::ThreadMain(void)
+{
+  for (;;) {
+    SandboxReport rep;
+    struct iovec iov;
+    struct msghdr msg;
+
+    iov.iov_base = &rep;
+    iov.iov_len = sizeof(rep);
+    PodZero(&msg);
+    msg.msg_iov = &iov;
+    msg.msg_iovlen = 1;
+
+    const auto recvd = recvmsg(mServerFd, &msg, 0);
+    if (recvd < 0) {
+      if (errno == EINTR) {
+	continue;
+      }
+      SANDBOX_LOG_ERROR("SandboxReporter: recvmsg: %s", strerror(errno));
+    }
+    if (recvd <= 0) {
+      break;
+    }
+
+    if (static_cast<size_t>(recvd) < sizeof(rep)) {
+      SANDBOX_LOG_ERROR("SandboxReporter: packet too short (%d < %d)",
+			recvd, sizeof(rep));
+      continue;
+    }
+    if (msg.msg_flags & MSG_TRUNC) {
+      SANDBOX_LOG_ERROR("SandboxReporter: packet too long");
+      continue;
+    }
+
+    AddOne(rep);
+  }
+}
+
+SandboxReporter::Snapshot
+SandboxReporter::GetSnapshot()
+{
+  Snapshot snapshot;
+  MutexAutoLock lock(mMutex);
+
+  const uint64_t bufSize = static_cast<uint64_t>(kSandboxReporterBufferSize);
+  const uint64_t start = std::max(mCount, bufSize) - bufSize;
+  snapshot.mOffset = start;
+  snapshot.mReports.Clear();
+  snapshot.mReports.SetCapacity(mCount - start);
+  for (size_t i = start; i < mCount; ++i) {
+    const SandboxReport* rep = &mBuffer[i % kSandboxReporterBufferSize];
+    MOZ_ASSERT(rep->IsValid());
+    snapshot.mReports.AppendElement(*rep);
+  }
+  // Named Return Value Optimization would apply here, but C++11
+  // doesn't require it; so, instead of possibly copying the entire
+  // array contents, invoke the move constructor and copy at most a
+  // few words.
+  return Move(snapshot);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporter.h
@@ -0,0 +1,87 @@
+/* -*- 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/. */
+
+#ifndef mozilla_SandboxReporter_h
+#define mozilla_SandboxReporter_h
+
+#include "SandboxReporterCommon.h"
+
+#include "base/platform_thread.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+// This object collects the SandboxReport messages from all of the
+// child processes, submits them to Telemetry, and maintains a ring
+// buffer of the last kSandboxReporterBufferSize reports.
+class SandboxReporter final
+  : public PlatformThread::Delegate
+{
+public:
+  // For normal use, don't construct this directly; use the
+  // Singleton() method.
+  //
+  // For unit testing, use this constructor followed by the Init
+  // method; the object isn't usable unless Init returns true.
+  explicit SandboxReporter();
+  ~SandboxReporter();
+
+  // See above; this method is not thread-safe.
+  bool Init();
+
+  // Used in GeckoChildProcessHost to connect the child process's
+  // client to this report collector.
+  void GetClientFileDescriptorMapping(int* aSrcFd, int* aDstFd) const;
+
+  // A snapshot of the report ring buffer; element 0 of `mReports` is
+  // the `mOffset`th report to be received, and so on.
+  struct Snapshot {
+    // The buffer has to fit in memory, but the total number of
+    // reports received in the session can increase without bound and
+    // could potentially overflow a uint32_t, so this is 64-bit.
+    // (It's exposed to JS as a 53-bit int, effectively, but that
+    // should also be large enough.)
+    uint64_t mOffset;
+    nsTArray<SandboxReport> mReports;
+  };
+
+  // Read the ring buffer contents; this method is thread-safe.
+  Snapshot GetSnapshot();
+
+  // Gets or creates the singleton report collector.  Crashes if
+  // initialization fails (if a socketpair and/or thread can't be
+  // created, there was almost certainly about to be a crash anyway).
+  // Thread-safe as long as the pointer isn't used during/after XPCOM
+  // shutdown.
+  static SandboxReporter* Singleton();
+private:
+  // These are constant over the life of the object:
+  int mClientFd;
+  int mServerFd;
+  PlatformThreadHandle mThread;
+
+  Mutex mMutex;
+  // These are protected by mMutex:
+  UniquePtr<SandboxReport[]> mBuffer;
+  uint64_t mCount;
+
+  static StaticAutoPtr<SandboxReporter> sSingleton;
+
+  void ThreadMain(void) override;
+  void AddOne(const SandboxReport& aReport);
+};
+
+// This is a constant so the % operations can be optimized.  This is
+// exposed in the header so that unit tests can see it.
+static const size_t kSandboxReporterBufferSize = 32;
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporter_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/reporter/SandboxReporterCommon.h
@@ -0,0 +1,62 @@
+/* -*- 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/. */
+
+#ifndef mozilla_SandboxReporterCommon_h
+#define mozilla_SandboxReporterCommon_h
+
+#include "mozilla/IntegerTypeTraits.h"
+#include "mozilla/Types.h"
+
+#include <sys/types.h>
+
+// Note: this is also used in libmozsandbox, so dependencies on
+// symbols from libxul probably won't work.
+
+namespace mozilla {
+static const size_t kSandboxSyscallArguments = 6;
+// fds 0-2: stdio; fd 3: IPC; fd 4: crash reporter.  (The IPC child
+// process launching code will check that we don't try to use the same
+// fd twice.)
+static const int kSandboxReporterFileDesc = 5;
+
+// This struct represents a system call that was rejected by a
+// seccomp-bpf policy.
+struct SandboxReport {
+  // In the future this may include finer distinctions than
+  // GeckoProcessType -- e.g., whether a content process can load
+  // file:/// URLs, or if it's reserved for content with certain
+  // user-granted permissions.
+  enum class ProcType : uint8_t {
+    CONTENT,
+    MEDIA_PLUGIN,
+  };
+
+  // The syscall number and arguments are usually `unsigned long`, but
+  // that causes ambiguous overload errors with nsACString::AppendInt.
+  using ULong = UnsignedStdintTypeForSize<sizeof(unsigned long)>::Type;
+
+  // This time uses CLOCK_MONOTONIC_COARSE.  Displaying or reporting
+  // it should usually be done relative to the current value of that
+  // clock (or the time at some other event of interest, like a
+  // subsequent crash).
+  struct timespec mTime;
+
+  // The pid/tid values, like every other field in this struct, aren't
+  // authenticated and a compromised process could send anything, so
+  // use the values with caution.
+  pid_t mPid;
+  pid_t mTid;
+  ProcType mProcType;
+  ULong mSyscall;
+  ULong mArgs[kSandboxSyscallArguments];
+
+  SandboxReport() : mPid(0) { }
+  bool IsValid() const { return mPid > 0; }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SandboxReporterCommon_h
new file mode 100644
--- /dev/null
+++ b/security/sandbox/linux/reporter/moz.build
@@ -0,0 +1,29 @@
+# -*- Mode: python; python-indent: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla += [
+    'SandboxReporter.h',
+    'SandboxReporterCommon.h',
+]
+
+SOURCES += [
+    'SandboxReporter.cpp',
+]
+
+LOCAL_INCLUDES += [
+    '/security/sandbox/linux', # SandboxLogging.h
+]
+
+# Need this for base::PlatformThread
+include('/ipc/chromium/chromium-config.mozbuild')
+
+# Need this for safe_sprintf.h used by SandboxLogging.h,
+# but it has to be after ipc/chromium/src.
+LOCAL_INCLUDES += [
+    '/security/sandbox/chromium',
+]
+
+FINAL_LIBRARY = 'xul'