297 lines
9.4 KiB
C++
297 lines
9.4 KiB
C++
/* -*- 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 "GeckoProfiler.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/StaticMutex.h"
|
|
#include "mozilla/PodOperations.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "mozilla/glean/SecuritySandboxMetrics.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"
|
|
#elif defined(__arm__)
|
|
# define SANDBOX_ARCH_NAME "arm"
|
|
#elif defined(__aarch64__)
|
|
# define SANDBOX_ARCH_NAME "arm64"
|
|
#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 | SOCK_CLOEXEC, 0, fds)) {
|
|
SANDBOX_LOG_ERRNO("SandboxReporter: socketpair failed");
|
|
return false;
|
|
}
|
|
mClientFd = fds[0];
|
|
mServerFd = fds[1];
|
|
|
|
if (!PlatformThread::Create(0, this, &mThread)) {
|
|
SANDBOX_LOG_ERRNO("SandboxReporter: thread creation failed");
|
|
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 MOZ_UNANNOTATED;
|
|
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.sys.mjs.
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"SandboxReporter::Singleton", [] { ClearOnShutdown(&sSingleton); }));
|
|
}
|
|
return sSingleton.get();
|
|
}
|
|
|
|
int SandboxReporter::GetClientFileDescriptor() const {
|
|
MOZ_ASSERT(mClientFd >= 0);
|
|
return mClientFd;
|
|
}
|
|
|
|
// 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::FILE:
|
|
key.AppendLiteral("file");
|
|
break;
|
|
case SandboxReport::ProcType::MEDIA_PLUGIN:
|
|
key.AppendLiteral("gmp");
|
|
break;
|
|
case SandboxReport::ProcType::RDD:
|
|
key.AppendLiteral("rdd");
|
|
break;
|
|
case SandboxReport::ProcType::SOCKET_PROCESS:
|
|
key.AppendLiteral("socket");
|
|
break;
|
|
case SandboxReport::ProcType::UTILITY:
|
|
key.AppendLiteral("utility");
|
|
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_HEX(ioctl, 1); // request
|
|
ARG_DECIMAL(fcntl, 1); // cmd
|
|
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);
|
|
}
|
|
|
|
glean::sandbox::rejected_syscalls.Get(key).Add(1);
|
|
}
|
|
|
|
void SandboxReporter::AddOne(const SandboxReport& aReport) {
|
|
SubmitToTelemetry(aReport);
|
|
|
|
MutexAutoLock lock(mMutex);
|
|
mBuffer[mCount % kSandboxReporterBufferSize] = aReport;
|
|
++mCount;
|
|
}
|
|
|
|
void SandboxReporter::ThreadMain(void) {
|
|
// Create a nsThread wrapper for the current platform thread, and register it
|
|
// with the thread manager.
|
|
(void)NS_GetCurrentThread();
|
|
|
|
PlatformThread::SetName("SandboxReporter");
|
|
AUTO_PROFILER_REGISTER_THREAD("SandboxReporter");
|
|
|
|
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_ERRNO("SandboxReporter: recvmsg");
|
|
}
|
|
if (recvd <= 0) {
|
|
break;
|
|
}
|
|
|
|
if (static_cast<size_t>(recvd) < sizeof(rep)) {
|
|
SANDBOX_LOG("SandboxReporter: packet too short (%d < %d)", recvd,
|
|
sizeof(rep));
|
|
continue;
|
|
}
|
|
if (msg.msg_flags & MSG_TRUNC) {
|
|
SANDBOX_LOG("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);
|
|
}
|
|
return snapshot;
|
|
}
|
|
|
|
} // namespace mozilla
|