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.
10 # include "mach_override.h"
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"
29 #include <sys/param.h>
31 #include <sys/socket.h>
38 #ifdef MOZ_REPLACE_MALLOC
39 # include "replace_malloc_bridge.h"
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 ********************************/
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
{
62 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp
, int aFd
)
63 : mozilla::IOInterposeObserver::Observation(
64 aOp
, sReference
, sIsEnabled
&& !mozilla::IsDebugFile(aFd
)),
66 mHasQueriedFilename(false) {}
68 MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp
, int aFd
,
69 const void* aBuf
, size_t aCount
)
70 : mozilla::IOInterposeObserver::Observation(
72 sIsEnabled
&& !mozilla::IsDebugFile(aFd
) &&
73 IsValidWrite(aFd
, aBuf
, aCount
)),
75 mHasQueriedFilename(false) {}
77 // Custom implementation of
78 // mozilla::IOInterposeObserver::Observation::Filename
79 void Filename(nsAString
& aFilename
) override
;
81 ~MacIOAutoObservation() { Report(); }
85 bool mHasQueriedFilename
;
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
;
100 char filename
[MAXPATHLEN
];
101 if (fcntl(mFd
, F_GETPATH
, filename
) != -1) {
102 CopyUTF8toUTF16(filename
, mFilename
);
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
) {
120 if ((aBuf
.st_mode
& S_IFMT
) != S_IFSOCK
) {
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.
142 int rv
= fstat(aFd
, &buf
);
147 if (IsIPCWrite(aFd
, buf
)) {
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.
158 // Break, here if we're allowed to report non-dirty writes
159 if (!sOnlyReportDirtyWrites
) {
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
);
171 off_t pos
= lseek(aFd
, 0, SEEK_CUR
);
175 ssize_t r
= read(aFd
, wbuf2
.get(), aCount
);
176 if (r
< 0 || (size_t)r
!= aCount
) {
179 int cmp
= memcmp(aWbuf
, wbuf2
.get(), aCount
);
183 off_t pos2
= lseek(aFd
, pos
, SEEK_SET
);
188 // Otherwise this is not a valid write
192 /*************************** Function Interception ***************************/
194 /** Structure for declaration of function override */
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
,
219 template <FuncData
& foo
>
220 ssize_t
wrap_pwrite_temp(int aFd
, const void* aBuf
, size_t aNumBytes
,
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
,
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
,
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
);
296 /******************************** IO Poisoning ********************************/
300 void InitPoisonIOInterposer() {
301 // Enable reporting from poisoned write methods
304 // Make sure we only poison writes once!
305 static bool WritesArePoisoned
= false;
306 if (WritesArePoisoned
) {
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
);
323 for (int i
= 0; i
< NumFunctions
; ++i
) {
324 FuncData
* d
= Functions
[i
];
326 d
->Function
= dlsym(RTLD_DEFAULT
, d
->Name
);
332 DebugOnly
<mach_error_t
> t
=
333 mach_override_ptr(d
->Function
, d
->Wrapper
, &d
->Buffer
);
334 MOZ_ASSERT(t
== err_none
);
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.
348 } // namespace mozilla