summaryrefslogtreecommitdiffstats
path: root/security/sandbox/linux/reporter/SandboxReporter.cpp
blob: a7c71cd5c961e506fc23fe59405673c322cfafc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
/* -*- 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/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"
#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();
}

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

  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) {
  // 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