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