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/. */
6
7 #include "PoisonIOInterposer.h"
8 // Disabled until bug 1658385 is fixed.
9 #ifndef __aarch64__
10 # include "mach_override.h"
11 #endif
12
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 "plstr.h"
25 #include "prio.h"
26
27 #include <algorithm>
28 #include <vector>
29
30 #include <sys/param.h>
31 #include <sys/stat.h>
32 #include <sys/socket.h>
33 #include <sys/uio.h>
34 #include <aio.h>
35 #include <dlfcn.h>
36 #include <fcntl.h>
37 #include <unistd.h>
38
39 #ifdef MOZ_REPLACE_MALLOC
40 # include "replace_malloc_bridge.h"
41 #endif
42
43 namespace {
44
45 // Bit tracking if poisoned writes are enabled
46 static bool sIsEnabled = false;
47
48 // Check if writes are dirty before reporting IO
49 static bool sOnlyReportDirtyWrites = false;
50
51 // Routines for write validation
52 bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount);
53 bool IsIPCWrite(int aFd, const struct stat& aBuf);
54
55 /******************************** IO AutoTimer ********************************/
56
57 /**
58 * RAII class for timing the duration of an I/O call and reporting the result
59 * to the mozilla::IOInterposeObserver API.
60 */
61 class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
62 public:
MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,int aFd)63 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd)
64 : mozilla::IOInterposeObserver::Observation(
65 aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)),
66 mFd(aFd),
67 mHasQueriedFilename(false) {}
68
MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,int aFd,const void * aBuf,size_t aCount)69 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd,
70 const void* aBuf, size_t aCount)
71 : mozilla::IOInterposeObserver::Observation(
72 aOp, sReference,
73 sIsEnabled && !mozilla::IsDebugFile(aFd) &&
74 IsValidWrite(aFd, aBuf, aCount)),
75 mFd(aFd),
76 mHasQueriedFilename(false) {}
77
78 // Custom implementation of
79 // mozilla::IOInterposeObserver::Observation::Filename
80 void Filename(nsAString& aFilename) override;
81
~MacIOAutoObservation()82 ~MacIOAutoObservation() { Report(); }
83
84 private:
85 int mFd;
86 bool mHasQueriedFilename;
87 nsString mFilename;
88 static const char* sReference;
89 };
90
91 const char* MacIOAutoObservation::sReference = "PoisonIOInterposer";
92
93 // Get filename for this observation
Filename(nsAString & aFilename)94 void MacIOAutoObservation::Filename(nsAString& aFilename) {
95 // If mHasQueriedFilename is true, then we already have it
96 if (mHasQueriedFilename) {
97 aFilename = mFilename;
98 return;
99 }
100
101 char filename[MAXPATHLEN];
102 if (fcntl(mFd, F_GETPATH, filename) != -1) {
103 CopyUTF8toUTF16(filename, mFilename);
104 } else {
105 mFilename.Truncate();
106 }
107 mHasQueriedFilename = true;
108
109 aFilename = mFilename;
110 }
111
112 /****************************** Write Validation ******************************/
113
114 // We want to detect "actual" writes, not IPC. Some IPC mechanisms are
115 // implemented with file descriptors, so filter them out.
IsIPCWrite(int aFd,const struct stat & aBuf)116 bool IsIPCWrite(int aFd, const struct stat& aBuf) {
117 if ((aBuf.st_mode & S_IFMT) == S_IFIFO) {
118 return true;
119 }
120
121 if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) {
122 return false;
123 }
124
125 sockaddr_storage address;
126 socklen_t len = sizeof(address);
127 if (getsockname(aFd, (sockaddr*)&address, &len) != 0) {
128 return true; // Ignore the aFd if we can't find what it is.
129 }
130
131 return address.ss_family == AF_UNIX;
132 }
133
134 // We want to report actual disk IO not things that don't move bits on the disk
IsValidWrite(int aFd,const void * aWbuf,size_t aCount)135 bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount) {
136 // Ignore writes of zero bytes, Firefox does some during shutdown.
137 if (aCount == 0) {
138 return false;
139 }
140
141 {
142 struct stat buf;
143 int rv = fstat(aFd, &buf);
144 if (rv != 0) {
145 return true;
146 }
147
148 if (IsIPCWrite(aFd, buf)) {
149 return false;
150 }
151 }
152
153 // For writev we pass a nullptr aWbuf. We should only get here from
154 // dbm, and it uses write, so assert that we have aWbuf.
155 if (!aWbuf) {
156 return true;
157 }
158
159 // Break, here if we're allowed to report non-dirty writes
160 if (!sOnlyReportDirtyWrites) {
161 return true;
162 }
163
164 // As a really bad hack, accept writes that don't change the on disk
165 // content. This is needed because dbm doesn't keep track of dirty bits
166 // and can end up writing the same data to disk twice. Once when the
167 // user (nss) asks it to sync and once when closing the database.
168 auto wbuf2 = mozilla::MakeUniqueFallible<char[]>(aCount);
169 if (!wbuf2) {
170 return true;
171 }
172 off_t pos = lseek(aFd, 0, SEEK_CUR);
173 if (pos == -1) {
174 return true;
175 }
176 ssize_t r = read(aFd, wbuf2.get(), aCount);
177 if (r < 0 || (size_t)r != aCount) {
178 return true;
179 }
180 int cmp = memcmp(aWbuf, wbuf2.get(), aCount);
181 if (cmp != 0) {
182 return true;
183 }
184 off_t pos2 = lseek(aFd, pos, SEEK_SET);
185 if (pos2 != pos) {
186 return true;
187 }
188
189 // Otherwise this is not a valid write
190 return false;
191 }
192
193 /*************************** Function Interception ***************************/
194
195 /** Structure for declaration of function override */
196 struct FuncData {
197 const char* Name; // Name of the function for the ones we use dlsym
198 const void* Wrapper; // The function that we will replace 'Function' with
199 void* Function; // The function that will be replaced with 'Wrapper'
200 void* Buffer; // Will point to the jump buffer that lets us call
201 // 'Function' after it has been replaced.
202 };
203
204 // Wrap aio_write. We have not seen it before, so just assert/report it.
205 typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp);
206 ssize_t wrap_aio_write(struct aiocb* aAioCbp);
207 FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write};
wrap_aio_write(struct aiocb * aAioCbp)208 ssize_t wrap_aio_write(struct aiocb* aAioCbp) {
209 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite,
210 aAioCbp->aio_fildes);
211
212 aio_write_t old_write = (aio_write_t)aio_write_data.Buffer;
213 return old_write(aAioCbp);
214 }
215
216 // Wrap pwrite-like functions.
217 // We have not seen them before, so just assert/report it.
218 typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes,
219 off_t aOffset);
220 template <FuncData& foo>
wrap_pwrite_temp(int aFd,const void * aBuf,size_t aNumBytes,off_t aOffset)221 ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes,
222 off_t aOffset) {
223 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd);
224 pwrite_t old_write = (pwrite_t)foo.Buffer;
225 return old_write(aFd, aBuf, aNumBytes, aOffset);
226 }
227
228 // Define a FuncData for a pwrite-like functions.
229 #define DEFINE_PWRITE_DATA(X, NAME) \
230 FuncData X##_data = {NAME, (void*)wrap_pwrite_temp<X##_data>};
231
232 // This exists everywhere.
233 DEFINE_PWRITE_DATA(pwrite, "pwrite")
234 // These exist on 32 bit OS X
235 DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003");
236 DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003");
237 // This exists on 64 bit OS X
238 DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL");
239
240 typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount);
241 template <FuncData& foo>
wrap_writev_temp(int aFd,const struct iovec * aIov,int aIovCount)242 ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) {
243 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd,
244 nullptr, aIovCount);
245 writev_t old_write = (writev_t)foo.Buffer;
246 return old_write(aFd, aIov, aIovCount);
247 }
248
249 // Define a FuncData for a writev-like functions.
250 #define DEFINE_WRITEV_DATA(X, NAME) \
251 FuncData X##_data = {NAME, (void*)wrap_writev_temp<X##_data>};
252
253 // This exists everywhere.
254 DEFINE_WRITEV_DATA(writev, "writev");
255 // These exist on 32 bit OS X
256 DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003");
257 DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003");
258 // This exists on 64 bit OS X
259 DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL");
260
261 typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount);
262 template <FuncData& foo>
wrap_write_temp(int aFd,const void * aBuf,size_t aCount)263 ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) {
264 MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf,
265 aCount);
266 write_t old_write = (write_t)foo.Buffer;
267 return old_write(aFd, aBuf, aCount);
268 }
269
270 // Define a FuncData for a write-like functions.
271 #define DEFINE_WRITE_DATA(X, NAME) \
272 FuncData X##_data = {NAME, (void*)wrap_write_temp<X##_data>};
273
274 // This exists everywhere.
275 DEFINE_WRITE_DATA(write, "write");
276 // These exist on 32 bit OS X
277 DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003");
278 DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003");
279 // This exists on 64 bit OS X
280 DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL");
281
282 FuncData* Functions[] = {&aio_write_data,
283
284 &pwrite_data, &pwrite_NOCANCEL_UNIX2003_data,
285 &pwrite_UNIX2003_data, &pwrite_NOCANCEL_data,
286
287 &write_data, &write_NOCANCEL_UNIX2003_data,
288 &write_UNIX2003_data, &write_NOCANCEL_data,
289
290 &writev_data, &writev_NOCANCEL_UNIX2003_data,
291 &writev_UNIX2003_data, &writev_NOCANCEL_data};
292
293 const int NumFunctions = mozilla::ArrayLength(Functions);
294
295 } // namespace
296
297 /******************************** IO Poisoning ********************************/
298
299 namespace mozilla {
300
InitPoisonIOInterposer()301 void InitPoisonIOInterposer() {
302 // Enable reporting from poisoned write methods
303 sIsEnabled = true;
304
305 // Make sure we only poison writes once!
306 static bool WritesArePoisoned = false;
307 if (WritesArePoisoned) {
308 return;
309 }
310 WritesArePoisoned = true;
311
312 // stdout and stderr are OK.
313 MozillaRegisterDebugFD(1);
314 MozillaRegisterDebugFD(2);
315
316 #ifdef MOZ_REPLACE_MALLOC
317 // The contract with InitDebugFd is that the given registry can be used
318 // at any moment, so the instance needs to persist longer than the scope
319 // of this functions.
320 static DebugFdRegistry registry;
321 ReplaceMalloc::InitDebugFd(registry);
322 #endif
323
324 for (int i = 0; i < NumFunctions; ++i) {
325 FuncData* d = Functions[i];
326 if (!d->Function) {
327 d->Function = dlsym(RTLD_DEFAULT, d->Name);
328 }
329 if (!d->Function) {
330 continue;
331 }
332 #ifndef __aarch64__
333 DebugOnly<mach_error_t> t =
334 mach_override_ptr(d->Function, d->Wrapper, &d->Buffer);
335 MOZ_ASSERT(t == err_none);
336 #endif
337 }
338 }
339
OnlyReportDirtyWrites()340 void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; }
341
342 // Never called! See bug 1647107.
ClearPoisonIOInterposer()343 void ClearPoisonIOInterposer() {
344 // Not sure how or if we can unpoison the functions. Would be nice, but no
345 // worries we won't need to do this anyway.
346 sIsEnabled = false;
347 }
348
349 } // namespace mozilla
350