Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / xpcom / build / PoisonIOInterposerMac.cpp
blob21eca58757d96f8e2d38c88bc5f28ad92698ee87
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PoisonIOInterposer.h"
8 // Disabled until bug 1658385 is fixed.
9 #ifndef __aarch64__
10 # include "mach_override.h"
11 #endif
13 #include "mozilla/ArrayUtils.h"
14 #include "mozilla/Assertions.h"
15 #include "mozilla/DebugOnly.h"
16 #include "mozilla/IOInterposer.h"
17 #include "mozilla/Mutex.h"
18 #include "mozilla/ProcessedStack.h"
19 #include "mozilla/Telemetry.h"
20 #include "mozilla/UniquePtrExtensions.h"
21 #include "nsPrintfCString.h"
22 #include "mozilla/StackWalk.h"
23 #include "nsTraceRefcnt.h"
24 #include "prio.h"
26 #include <algorithm>
27 #include <vector>
29 #include <sys/param.h>
30 #include <sys/stat.h>
31 #include <sys/socket.h>
32 #include <sys/uio.h>
33 #include <aio.h>
34 #include <dlfcn.h>
35 #include <fcntl.h>
36 #include <unistd.h>
38 #ifdef MOZ_REPLACE_MALLOC
39 # include "replace_malloc_bridge.h"
40 #endif
42 namespace {
44 // Bit tracking if poisoned writes are enabled
45 static bool sIsEnabled = false;
47 // Check if writes are dirty before reporting IO
48 static bool sOnlyReportDirtyWrites = false;
50 // Routines for write validation
51 bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount);
52 bool IsIPCWrite(int aFd, const struct stat& aBuf);
54 /******************************** IO AutoTimer ********************************/
56 /**
57 * RAII class for timing the duration of an I/O call and reporting the result
58 * to the mozilla::IOInterposeObserver API.
60 class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
61 public:
62 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd)
63 : mozilla::IOInterposeObserver::Observation(
64 aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)),
65 mFd(aFd),
66 mHasQueriedFilename(false) {}
68 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd,
69 const void* aBuf, size_t aCount)
70 : mozilla::IOInterposeObserver::Observation(
71 aOp, sReference,
72 sIsEnabled && !mozilla::IsDebugFile(aFd) &&
73 IsValidWrite(aFd, aBuf, aCount)),
74 mFd(aFd),
75 mHasQueriedFilename(false) {}
77 // Custom implementation of
78 // mozilla::IOInterposeObserver::Observation::Filename
79 void Filename(nsAString& aFilename) override;
81 ~MacIOAutoObservation() { Report(); }
83 private:
84 int mFd;
85 bool mHasQueriedFilename;
86 nsString mFilename;
87 static const char* sReference;
90 const char* MacIOAutoObservation::sReference = "PoisonIOInterposer";
92 // Get filename for this observation
93 void MacIOAutoObservation::Filename(nsAString& aFilename) {
94 // If mHasQueriedFilename is true, then we already have it
95 if (mHasQueriedFilename) {
96 aFilename = mFilename;
97 return;
100 char filename[MAXPATHLEN];
101 if (fcntl(mFd, F_GETPATH, filename) != -1) {
102 CopyUTF8toUTF16(filename, mFilename);
103 } else {
104 mFilename.Truncate();
106 mHasQueriedFilename = true;
108 aFilename = mFilename;
111 /****************************** Write Validation ******************************/
113 // We want to detect "actual" writes, not IPC. Some IPC mechanisms are
114 // implemented with file descriptors, so filter them out.
115 bool IsIPCWrite(int aFd, const struct stat& aBuf) {
116 if ((aBuf.st_mode & S_IFMT) == S_IFIFO) {
117 return true;
120 if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) {
121 return false;
124 sockaddr_storage address;
125 socklen_t len = sizeof(address);
126 if (getsockname(aFd, (sockaddr*)&address, &len) != 0) {
127 return true; // Ignore the aFd if we can't find what it is.
130 return address.ss_family == AF_UNIX;
133 // We want to report actual disk IO not things that don't move bits on the disk
134 bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount) {
135 // Ignore writes of zero bytes, Firefox does some during shutdown.
136 if (aCount == 0) {
137 return false;
141 struct stat buf;
142 int rv = fstat(aFd, &buf);
143 if (rv != 0) {
144 return true;
147 if (IsIPCWrite(aFd, buf)) {
148 return false;
152 // For writev we pass a nullptr aWbuf. We should only get here from
153 // dbm, and it uses write, so assert that we have aWbuf.
154 if (!aWbuf) {
155 return true;
158 // Break, here if we're allowed to report non-dirty writes
159 if (!sOnlyReportDirtyWrites) {
160 return true;
163 // As a really bad hack, accept writes that don't change the on disk
164 // content. This is needed because dbm doesn't keep track of dirty bits
165 // and can end up writing the same data to disk twice. Once when the
166 // user (nss) asks it to sync and once when closing the database.
167 auto wbuf2 = mozilla::MakeUniqueFallible<char[]>(aCount);
168 if (!wbuf2) {
169 return true;
171 off_t pos = lseek(aFd, 0, SEEK_CUR);
172 if (pos == -1) {
173 return true;
175 ssize_t r = read(aFd, wbuf2.get(), aCount);
176 if (r < 0 || (size_t)r != aCount) {
177 return true;
179 int cmp = memcmp(aWbuf, wbuf2.get(), aCount);
180 if (cmp != 0) {
181 return true;
183 off_t pos2 = lseek(aFd, pos, SEEK_SET);
184 if (pos2 != pos) {
185 return true;
188 // Otherwise this is not a valid write
189 return false;
192 /*************************** Function Interception ***************************/
194 /** Structure for declaration of function override */
195 struct FuncData {
196 const char* Name; // Name of the function for the ones we use dlsym
197 const void* Wrapper; // The function that we will replace 'Function' with
198 void* Function; // The function that will be replaced with 'Wrapper'
199 void* Buffer; // Will point to the jump buffer that lets us call
200 // 'Function' after it has been replaced.
203 // Wrap aio_write. We have not seen it before, so just assert/report it.
204 typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp);
205 ssize_t wrap_aio_write(struct aiocb* aAioCbp);
206 FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write};
207 ssize_t wrap_aio_write(struct aiocb* aAioCbp) {
208 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite,
209 aAioCbp->aio_fildes);
211 aio_write_t old_write = (aio_write_t)aio_write_data.Buffer;
212 return old_write(aAioCbp);
215 // Wrap pwrite-like functions.
216 // We have not seen them before, so just assert/report it.
217 typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes,
218 off_t aOffset);
219 template <FuncData& foo>
220 ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes,
221 off_t aOffset) {
222 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd);
223 pwrite_t old_write = (pwrite_t)foo.Buffer;
224 return old_write(aFd, aBuf, aNumBytes, aOffset);
227 // Define a FuncData for a pwrite-like functions.
228 #define DEFINE_PWRITE_DATA(X, NAME) \
229 FuncData X##_data = {NAME, (void*)wrap_pwrite_temp<X##_data>};
231 // This exists everywhere.
232 DEFINE_PWRITE_DATA(pwrite, "pwrite")
233 // These exist on 32 bit OS X
234 DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
235 DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
236 // This exists on 64 bit OS X
237 DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
239 typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount);
240 template <FuncData& foo>
241 ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) {
242 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd,
243 nullptr, aIovCount);
244 writev_t old_write = (writev_t)foo.Buffer;
245 return old_write(aFd, aIov, aIovCount);
248 // Define a FuncData for a writev-like functions.
249 #define DEFINE_WRITEV_DATA(X, NAME) \
250 FuncData X##_data = {NAME, (void*)wrap_writev_temp<X##_data>};
252 // This exists everywhere.
253 DEFINE_WRITEV_DATA(writev, "writev");
254 // These exist on 32 bit OS X
255 DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
256 DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
257 // This exists on 64 bit OS X
258 DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
260 typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount);
261 template <FuncData& foo>
262 ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) {
263 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf,
264 aCount);
265 write_t old_write = (write_t)foo.Buffer;
266 return old_write(aFd, aBuf, aCount);
269 // Define a FuncData for a write-like functions.
270 #define DEFINE_WRITE_DATA(X, NAME) \
271 FuncData X##_data = {NAME, (void*)wrap_write_temp<X##_data>};
273 // This exists everywhere.
274 DEFINE_WRITE_DATA(write, "write");
275 // These exist on 32 bit OS X
276 DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
277 DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
278 // This exists on 64 bit OS X
279 DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");
281 FuncData* Functions[] = {&aio_write_data,
283 &pwrite_data, &pwrite_NOCANCEL_UNIX2003_data,
284 &pwrite_UNIX2003_data, &pwrite_NOCANCEL_data,
286 &write_data, &write_NOCANCEL_UNIX2003_data,
287 &write_UNIX2003_data, &write_NOCANCEL_data,
289 &writev_data, &writev_NOCANCEL_UNIX2003_data,
290 &writev_UNIX2003_data, &writev_NOCANCEL_data};
292 const int NumFunctions = mozilla::ArrayLength(Functions);
294 } // namespace
296 /******************************** IO Poisoning ********************************/
298 namespace mozilla {
300 void InitPoisonIOInterposer() {
301 // Enable reporting from poisoned write methods
302 sIsEnabled = true;
304 // Make sure we only poison writes once!
305 static bool WritesArePoisoned = false;
306 if (WritesArePoisoned) {
307 return;
309 WritesArePoisoned = true;
311 // stdout and stderr are OK.
312 MozillaRegisterDebugFD(1);
313 MozillaRegisterDebugFD(2);
315 #ifdef MOZ_REPLACE_MALLOC
316 // The contract with InitDebugFd is that the given registry can be used
317 // at any moment, so the instance needs to persist longer than the scope
318 // of this functions.
319 static DebugFdRegistry registry;
320 ReplaceMalloc::InitDebugFd(registry);
321 #endif
323 for (int i = 0; i < NumFunctions; ++i) {
324 FuncData* d = Functions[i];
325 if (!d->Function) {
326 d->Function = dlsym(RTLD_DEFAULT, d->Name);
328 if (!d->Function) {
329 continue;
331 #ifndef __aarch64__
332 DebugOnly<mach_error_t> t =
333 mach_override_ptr(d->Function, d->Wrapper, &d->Buffer);
334 MOZ_ASSERT(t == err_none);
335 #endif
339 void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; }
341 // Never called! See bug 1647107.
342 void ClearPoisonIOInterposer() {
343 // Not sure how or if we can unpoison the functions. Would be nice, but no
344 // worries we won't need to do this anyway.
345 sIsEnabled = false;
348 } // namespace mozilla