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