/* -*- 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 <time.h> // for clockid_t

#include "mozilla/Assertions.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/PodOperations.h"
#include "nsThreadUtils.h"
#include "mozilla/Telemetry.h"
#include "sandbox/linux/system_headers/linux_syscalls.h"

// Distinguish architectures for the telemetry key.
#if defined(__i386__)
#define SANDBOX_ARCH_NAME "x86"
#elif defined(__x86_64__)
#define SANDBOX_ARCH_NAME "amd64"
#else
#error "unrecognized architecture"
#endif

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;
}

// This function is mentioned in Histograms.json; keep that in mind if
// it's renamed or moved to a different file.
static void
SubmitToTelemetry(const SandboxReport& aReport)
{
  nsAutoCString key;
  // The key contains the process type, something that uniquely
  // identifies the syscall, and in some cases arguments (see below
  // for details).  Arbitrary formatting choice: fields in the key are
  // separated by ':', except that (arch, syscall#) pairs are
  // separated by '/'.
  //
  // Examples:
  // * "content:x86/64"           (bug 1285768)
  // * "content:x86_64/110"       (bug 1285768)
  // * "gmp:madvise:8"            (bug 1303813)
  // * "content:clock_gettime:4"  (bug 1334687)

  switch (aReport.mProcType) {
  case SandboxReport::ProcType::CONTENT:
    key.AppendLiteral("content");
    break;
  case SandboxReport::ProcType::MEDIA_PLUGIN:
    key.AppendLiteral("gmp");
    break;
  default:
    MOZ_ASSERT(false);
  }
  key.Append(':');

  switch(aReport.mSyscall) {
    // Syscalls that are filtered by arguments in one or more of the
    // policies in SandboxFilter.cpp should generally have those
    // arguments included here, but don't include irrelevant
    // information that would cause large numbers of distinct keys for
    // the same issue -- for example, pids or pointers.  When in
    // doubt, include arguments only if they would typically be
    // constants (or asm immediates) in the code making the syscall.
    //
    // Also, keep in mind that this is opt-out data collection and
    // privacy is critical.  While it's unlikely that information in
    // the register values alone could personally identify a user
    // (see also crash reports, where register contents are public),
    // and the guidelines in the previous paragraph should rule out
    // any value that's capable of holding PII, please be careful.
    //
    // When making changes here, please consult with a data steward
    // (https://wiki.mozilla.org/Firefox/Data_Collection) and ask for
    // a review if you are unsure about anything.

    // This macro includes one argument as a decimal number; it should
    // be enough for most cases.
#define ARG_DECIMAL(name, idx)           \
    case __NR_##name:                    \
      key.AppendLiteral(#name ":");      \
      key.AppendInt(aReport.mArgs[idx]); \
      break

    // This may be more convenient if the argument is a set of bit flags.
#define ARG_HEX(name, idx)                    \
    case __NR_##name:                         \
      key.AppendLiteral(#name ":0x");         \
      key.AppendInt(aReport.mArgs[idx], 16);  \
      break

    // clockid_t is annoying: there are a small set of fixed timers,
    // but it can also encode a pid/tid (or a fd for a hardware clock
    // device); in this case the value is negative.
#define ARG_CLOCKID(name, idx)                              \
    case __NR_##name:                                       \
      key.AppendLiteral(#name ":");                         \
      if (static_cast<clockid_t>(aReport.mArgs[idx]) < 0) { \
        key.AppendLiteral("dynamic");                       \
      } else {                                              \
        key.AppendInt(aReport.mArgs[idx]);                  \
      }                                                     \
      break

    // The syscalls handled specially:

    ARG_HEX(clone, 0); // flags
    ARG_DECIMAL(prctl, 0); // option
    ARG_DECIMAL(madvise, 2); // advice
    ARG_CLOCKID(clock_gettime, 0); // clk_id

#ifdef __NR_socketcall
    ARG_DECIMAL(socketcall, 0); // call
#endif
#ifdef __NR_ipc
    ARG_DECIMAL(ipc, 0); // call
#endif

#undef ARG_DECIMAL
#undef ARG_HEX
#undef ARG_CLOCKID

  default:
    // Otherwise just use the number, with the arch name to disambiguate.
    key.Append(SANDBOX_ARCH_NAME "/");
    key.AppendInt(aReport.mSyscall);
  }

  Telemetry::Accumulate(Telemetry::SANDBOX_REJECTED_SYSCALLS, key);
}

void
SandboxReporter::AddOne(const SandboxReport& aReport)
{
  SubmitToTelemetry(aReport);

  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
