summaryrefslogtreecommitdiffstats
path: root/xpcom/build/PoisonIOInterposerMac.cpp
blob: 46dc75122143ecb372dc6769a11db3627df76054 (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
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/* -*- 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 "PoisonIOInterposer.h"
// Disabled until bug 1658385 is fixed.
#ifndef __aarch64__
#  include "mach_override.h"
#endif

#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/IOInterposer.h"
#include "mozilla/Mutex.h"
#include "mozilla/ProcessedStack.h"
#include "mozilla/Telemetry.h"
#include "mozilla/UniquePtrExtensions.h"
#include "nsPrintfCString.h"
#include "mozilla/StackWalk.h"
#include "nsTraceRefcnt.h"
#include "prio.h"

#include <algorithm>
#include <vector>

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <aio.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>

#ifdef MOZ_REPLACE_MALLOC
#  include "replace_malloc_bridge.h"
#endif

namespace {

// Bit tracking if poisoned writes are enabled
static bool sIsEnabled = false;

// Check if writes are dirty before reporting IO
static bool sOnlyReportDirtyWrites = false;

// Routines for write validation
bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount);
bool IsIPCWrite(int aFd, const struct stat& aBuf);

/******************************** IO AutoTimer ********************************/

/**
 * RAII class for timing the duration of an I/O call and reporting the result
 * to the mozilla::IOInterposeObserver API.
 */
class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
 public:
  MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd)
      : mozilla::IOInterposeObserver::Observation(
            aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)),
        mFd(aFd),
        mHasQueriedFilename(false) {}

  MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd,
                       const void* aBuf, size_t aCount)
      : mozilla::IOInterposeObserver::Observation(
            aOp, sReference,
            sIsEnabled && !mozilla::IsDebugFile(aFd) &&
                IsValidWrite(aFd, aBuf, aCount)),
        mFd(aFd),
        mHasQueriedFilename(false) {}

  // Custom implementation of
  // mozilla::IOInterposeObserver::Observation::Filename
  void Filename(nsAString& aFilename) override;

  ~MacIOAutoObservation() { Report(); }

 private:
  int mFd;
  bool mHasQueriedFilename;
  nsString mFilename;
  static const char* sReference;
};

const char* MacIOAutoObservation::sReference = "PoisonIOInterposer";

// Get filename for this observation
void MacIOAutoObservation::Filename(nsAString& aFilename) {
  // If mHasQueriedFilename is true, then we already have it
  if (mHasQueriedFilename) {
    aFilename = mFilename;
    return;
  }

  char filename[MAXPATHLEN];
  if (fcntl(mFd, F_GETPATH, filename) != -1) {
    CopyUTF8toUTF16(filename, mFilename);
  } else {
    mFilename.Truncate();
  }
  mHasQueriedFilename = true;

  aFilename = mFilename;
}

/****************************** Write Validation ******************************/

// We want to detect "actual" writes, not IPC. Some IPC mechanisms are
// implemented with file descriptors, so filter them out.
bool IsIPCWrite(int aFd, const struct stat& aBuf) {
  if ((aBuf.st_mode & S_IFMT) == S_IFIFO) {
    return true;
  }

  if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) {
    return false;
  }

  sockaddr_storage address;
  socklen_t len = sizeof(address);
  if (getsockname(aFd, (sockaddr*)&address, &len) != 0) {
    return true;  // Ignore the aFd if we can't find what it is.
  }

  return address.ss_family == AF_UNIX;
}

// We want to report actual disk IO not things that don't move bits on the disk
bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount) {
  // Ignore writes of zero bytes, Firefox does some during shutdown.
  if (aCount == 0) {
    return false;
  }

  {
    struct stat buf;
    int rv = fstat(aFd, &buf);
    if (rv != 0) {
      return true;
    }

    if (IsIPCWrite(aFd, buf)) {
      return false;
    }
  }

  // For writev we pass a nullptr aWbuf. We should only get here from
  // dbm, and it uses write, so assert that we have aWbuf.
  if (!aWbuf) {
    return true;
  }

  // Break, here if we're allowed to report non-dirty writes
  if (!sOnlyReportDirtyWrites) {
    return true;
  }

  // As a really bad hack, accept writes that don't change the on disk
  // content. This is needed because dbm doesn't keep track of dirty bits
  // and can end up writing the same data to disk twice. Once when the
  // user (nss) asks it to sync and once when closing the database.
  auto wbuf2 = mozilla::MakeUniqueFallible<char[]>(aCount);
  if (!wbuf2) {
    return true;
  }
  off_t pos = lseek(aFd, 0, SEEK_CUR);
  if (pos == -1) {
    return true;
  }
  ssize_t r = read(aFd, wbuf2.get(), aCount);
  if (r < 0 || (size_t)r != aCount) {
    return true;
  }
  int cmp = memcmp(aWbuf, wbuf2.get(), aCount);
  if (cmp != 0) {
    return true;
  }
  off_t pos2 = lseek(aFd, pos, SEEK_SET);
  if (pos2 != pos) {
    return true;
  }

  // Otherwise this is not a valid write
  return false;
}

/*************************** Function Interception  ***************************/

/** Structure for declaration of function override */
struct FuncData {
  const char* Name;     // Name of the function for the ones we use dlsym
  const void* Wrapper;  // The function that we will replace 'Function' with
  void* Function;       // The function that will be replaced with 'Wrapper'
  void* Buffer;         // Will point to the jump buffer that lets us call
                        // 'Function' after it has been replaced.
};

// Wrap aio_write. We have not seen it before, so just assert/report it.
typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp);
ssize_t wrap_aio_write(struct aiocb* aAioCbp);
FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write};
ssize_t wrap_aio_write(struct aiocb* aAioCbp) {
  MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite,
                             aAioCbp->aio_fildes);

  aio_write_t old_write = (aio_write_t)aio_write_data.Buffer;
  return old_write(aAioCbp);
}

// Wrap pwrite-like functions.
// We have not seen them before, so just assert/report it.
typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes,
                            off_t aOffset);
template <FuncData& foo>
ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes,
                         off_t aOffset) {
  MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd);
  pwrite_t old_write = (pwrite_t)foo.Buffer;
  return old_write(aFd, aBuf, aNumBytes, aOffset);
}

// Define a FuncData for a pwrite-like functions.
#define DEFINE_PWRITE_DATA(X, NAME) \
  FuncData X##_data = {NAME, (void*)wrap_pwrite_temp<X##_data>};

// This exists everywhere.
DEFINE_PWRITE_DATA(pwrite, "pwrite")
// These exist on 32 bit OS X
DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
// This exists on 64 bit OS X
DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");

typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount);
template <FuncData& foo>
ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) {
  MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd,
                             nullptr, aIovCount);
  writev_t old_write = (writev_t)foo.Buffer;
  return old_write(aFd, aIov, aIovCount);
}

// Define a FuncData for a writev-like functions.
#define DEFINE_WRITEV_DATA(X, NAME) \
  FuncData X##_data = {NAME, (void*)wrap_writev_temp<X##_data>};

// This exists everywhere.
DEFINE_WRITEV_DATA(writev, "writev");
// These exist on 32 bit OS X
DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
// This exists on 64 bit OS X
DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");

typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount);
template <FuncData& foo>
ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) {
  MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf,
                             aCount);
  write_t old_write = (write_t)foo.Buffer;
  return old_write(aFd, aBuf, aCount);
}

// Define a FuncData for a write-like functions.
#define DEFINE_WRITE_DATA(X, NAME) \
  FuncData X##_data = {NAME, (void*)wrap_write_temp<X##_data>};

// This exists everywhere.
DEFINE_WRITE_DATA(write, "write");
// These exist on 32 bit OS X
DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
// This exists on 64 bit OS X
DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");

FuncData* Functions[] = {&aio_write_data,

                         &pwrite_data,          &pwrite_NOCANCEL_UNIX2003_data,
                         &pwrite_UNIX2003_data, &pwrite_NOCANCEL_data,

                         &write_data,           &write_NOCANCEL_UNIX2003_data,
                         &write_UNIX2003_data,  &write_NOCANCEL_data,

                         &writev_data,          &writev_NOCANCEL_UNIX2003_data,
                         &writev_UNIX2003_data, &writev_NOCANCEL_data};

const int NumFunctions = mozilla::ArrayLength(Functions);

}  // namespace

/******************************** IO Poisoning ********************************/

namespace mozilla {

void InitPoisonIOInterposer() {
  // Enable reporting from poisoned write methods
  sIsEnabled = true;

  // Make sure we only poison writes once!
  static bool WritesArePoisoned = false;
  if (WritesArePoisoned) {
    return;
  }
  WritesArePoisoned = true;

  // stdout and stderr are OK.
  MozillaRegisterDebugFD(1);
  MozillaRegisterDebugFD(2);

#ifdef MOZ_REPLACE_MALLOC
  // The contract with InitDebugFd is that the given registry can be used
  // at any moment, so the instance needs to persist longer than the scope
  // of this functions.
  static DebugFdRegistry registry;
  ReplaceMalloc::InitDebugFd(registry);
#endif

  for (int i = 0; i < NumFunctions; ++i) {
    FuncData* d = Functions[i];
    if (!d->Function) {
      d->Function = dlsym(RTLD_DEFAULT, d->Name);
    }
    if (!d->Function) {
      continue;
    }

    // Disable the interposer on arm64 until there's
    // a mach_override_ptr implementation.
#ifndef __aarch64__
    // Temporarily disable the interposer on macOS x64
    // while dynamic code disablement rides the trains.
#  ifdef MOZ_INTERPOSER_FORCE_MACOS_X64
    DebugOnly<mach_error_t> t =
        mach_override_ptr(d->Function, d->Wrapper, &d->Buffer);
    MOZ_ASSERT(t == err_none);
#  endif  // MOZ_INTERPOSER_FORCE_MACOS_X64
#endif
  }
}

void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; }

// Never called! See bug 1647107.
void ClearPoisonIOInterposer() {
  // Not sure how or if we can unpoison the functions. Would be nice, but no
  // worries we won't need to do this anyway.
  sIsEnabled = false;
}

}  // namespace mozilla