michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 ci et: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "PoisonIOInterposer.h" michael@0: #include "mach_override.h" michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/ProcessedStack.h" michael@0: #include "mozilla/Scoped.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsStackWalk.h" michael@0: #include "nsTraceRefcnt.h" michael@0: #include "plstr.h" michael@0: #include "prio.h" michael@0: michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: namespace { michael@0: michael@0: using namespace mozilla; michael@0: michael@0: // Bit tracking if poisoned writes are enabled michael@0: static bool sIsEnabled = false; michael@0: michael@0: // Check if writes are dirty before reporting IO michael@0: static bool sOnlyReportDirtyWrites = false; michael@0: michael@0: // Routines for write validation michael@0: bool IsValidWrite(int fd, const void *wbuf, size_t count); michael@0: bool IsIPCWrite(int fd, const struct stat &buf); michael@0: michael@0: /******************************** IO AutoTimer ********************************/ michael@0: michael@0: /** michael@0: * RAII class for timing the duration of an I/O call and reporting the result michael@0: * to the IOInterposeObserver API. michael@0: */ michael@0: class MacIOAutoObservation : public IOInterposeObserver::Observation michael@0: { michael@0: public: michael@0: MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd) michael@0: : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && michael@0: !IsDebugFile(aFd)) michael@0: , mFd(aFd) michael@0: , mHasQueriedFilename(false) michael@0: , mFilename(nullptr) michael@0: { michael@0: } michael@0: michael@0: MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd, michael@0: const void *aBuf, size_t aCount) michael@0: : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && michael@0: !IsDebugFile(aFd) && michael@0: IsValidWrite(aFd, aBuf, aCount)) michael@0: , mFd(aFd) michael@0: , mHasQueriedFilename(false) michael@0: , mFilename(nullptr) michael@0: { michael@0: } michael@0: michael@0: // Custom implementation of IOInterposeObserver::Observation::Filename michael@0: const char16_t* Filename() MOZ_OVERRIDE; michael@0: michael@0: ~MacIOAutoObservation() michael@0: { michael@0: Report(); michael@0: if (mFilename) { michael@0: NS_Free(mFilename); michael@0: mFilename = nullptr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: int mFd; michael@0: bool mHasQueriedFilename; michael@0: char16_t* mFilename; michael@0: static const char* sReference; michael@0: }; michael@0: michael@0: const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; michael@0: michael@0: // Get filename for this observation michael@0: const char16_t* MacIOAutoObservation::Filename() michael@0: { michael@0: // If mHasQueriedFilename is true, then we already have it michael@0: if (mHasQueriedFilename) { michael@0: return mFilename; michael@0: } michael@0: char filename[MAXPATHLEN]; michael@0: if (fcntl(mFd, F_GETPATH, filename) != -1) { michael@0: mFilename = UTF8ToNewUnicode(nsDependentCString(filename)); michael@0: } else { michael@0: mFilename = nullptr; michael@0: } michael@0: mHasQueriedFilename = true; michael@0: michael@0: // Return filename michael@0: return mFilename; michael@0: } michael@0: michael@0: /****************************** Write Validation ******************************/ michael@0: michael@0: // We want to detect "actual" writes, not IPC. Some IPC mechanisms are michael@0: // implemented with file descriptors, so filter them out. michael@0: bool IsIPCWrite(int fd, const struct stat &buf) { michael@0: if ((buf.st_mode & S_IFMT) == S_IFIFO) { michael@0: return true; michael@0: } michael@0: michael@0: if ((buf.st_mode & S_IFMT) != S_IFSOCK) { michael@0: return false; michael@0: } michael@0: michael@0: sockaddr_storage address; michael@0: socklen_t len = sizeof(address); michael@0: if (getsockname(fd, (sockaddr*) &address, &len) != 0) { michael@0: return true; // Ignore the fd if we can't find what it is. michael@0: } michael@0: michael@0: return address.ss_family == AF_UNIX; michael@0: } michael@0: michael@0: // We want to report actual disk IO not things that don't move bits on the disk michael@0: bool IsValidWrite(int fd, const void *wbuf, size_t count) michael@0: { michael@0: // Ignore writes of zero bytes, Firefox does some during shutdown. michael@0: if (count == 0) { michael@0: return false; michael@0: } michael@0: michael@0: { michael@0: struct stat buf; michael@0: int rv = fstat(fd, &buf); michael@0: if (rv != 0) { michael@0: return true; michael@0: } michael@0: michael@0: if (IsIPCWrite(fd, buf)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // For writev we pass a nullptr wbuf. We should only get here from michael@0: // dbm, and it uses write, so assert that we have wbuf. michael@0: if (!wbuf) { michael@0: return true; michael@0: } michael@0: michael@0: // Break, here if we're allowed to report non-dirty writes michael@0: if(!sOnlyReportDirtyWrites) { michael@0: return true; michael@0: } michael@0: michael@0: // As a really bad hack, accept writes that don't change the on disk michael@0: // content. This is needed because dbm doesn't keep track of dirty bits michael@0: // and can end up writing the same data to disk twice. Once when the michael@0: // user (nss) asks it to sync and once when closing the database. michael@0: ScopedFreePtr wbuf2(malloc(count)); michael@0: if (!wbuf2) { michael@0: return true; michael@0: } michael@0: off_t pos = lseek(fd, 0, SEEK_CUR); michael@0: if (pos == -1) { michael@0: return true; michael@0: } michael@0: ssize_t r = read(fd, wbuf2, count); michael@0: if (r < 0 || (size_t)r != count) { michael@0: return true; michael@0: } michael@0: int cmp = memcmp(wbuf, wbuf2, count); michael@0: if (cmp != 0) { michael@0: return true; michael@0: } michael@0: off_t pos2 = lseek(fd, pos, SEEK_SET); michael@0: if (pos2 != pos) { michael@0: return true; michael@0: } michael@0: michael@0: // Otherwise this is not a valid write michael@0: return false; michael@0: } michael@0: michael@0: /*************************** Function Interception ***************************/ michael@0: michael@0: /** Structure for declaration of function override */ michael@0: struct FuncData { michael@0: const char *Name; // Name of the function for the ones we use dlsym michael@0: const void *Wrapper; // The function that we will replace 'Function' with michael@0: void *Function; // The function that will be replaced with 'Wrapper' michael@0: void *Buffer; // Will point to the jump buffer that lets us call michael@0: // 'Function' after it has been replaced. michael@0: }; michael@0: michael@0: // Wrap aio_write. We have not seen it before, so just assert/report it. michael@0: typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp); michael@0: ssize_t wrap_aio_write(struct aiocb *aiocbp); michael@0: FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write }; michael@0: ssize_t wrap_aio_write(struct aiocb *aiocbp) { michael@0: MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aiocbp->aio_fildes); michael@0: michael@0: aio_write_t old_write = (aio_write_t) aio_write_data.Buffer; michael@0: return old_write(aiocbp); michael@0: } michael@0: michael@0: // Wrap pwrite-like functions. michael@0: // We have not seen them before, so just assert/report it. michael@0: typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset); michael@0: template michael@0: ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) { michael@0: MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd); michael@0: pwrite_t old_write = (pwrite_t) foo.Buffer; michael@0: return old_write(fd, buf, nbyte, offset); michael@0: } michael@0: michael@0: // Define a FuncData for a pwrite-like functions. michael@0: #define DEFINE_PWRITE_DATA(X, NAME) \ michael@0: FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp }; \ michael@0: michael@0: // This exists everywhere. michael@0: DEFINE_PWRITE_DATA(pwrite, "pwrite") michael@0: // These exist on 32 bit OS X michael@0: DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003"); michael@0: DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003"); michael@0: // This exists on 64 bit OS X michael@0: DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); michael@0: michael@0: michael@0: typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt); michael@0: template michael@0: ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) { michael@0: MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, nullptr, iovcnt); michael@0: writev_t old_write = (writev_t) foo.Buffer; michael@0: return old_write(fd, iov, iovcnt); michael@0: } michael@0: michael@0: // Define a FuncData for a writev-like functions. michael@0: #define DEFINE_WRITEV_DATA(X, NAME) \ michael@0: FuncData X ## _data = { NAME, (void*) wrap_writev_temp }; \ michael@0: michael@0: // This exists everywhere. michael@0: DEFINE_WRITEV_DATA(writev, "writev"); michael@0: // These exist on 32 bit OS X michael@0: DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003"); michael@0: DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003"); michael@0: // This exists on 64 bit OS X michael@0: DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); michael@0: michael@0: typedef ssize_t (*write_t)(int fd, const void *buf, size_t count); michael@0: template michael@0: ssize_t wrap_write_temp(int fd, const void *buf, size_t count) { michael@0: MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, buf, count); michael@0: write_t old_write = (write_t) foo.Buffer; michael@0: return old_write(fd, buf, count); michael@0: } michael@0: michael@0: // Define a FuncData for a write-like functions. michael@0: #define DEFINE_WRITE_DATA(X, NAME) \ michael@0: FuncData X ## _data = { NAME, (void*) wrap_write_temp }; \ michael@0: michael@0: // This exists everywhere. michael@0: DEFINE_WRITE_DATA(write, "write"); michael@0: // These exist on 32 bit OS X michael@0: DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003"); michael@0: DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003"); michael@0: // This exists on 64 bit OS X michael@0: DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL"); michael@0: michael@0: FuncData *Functions[] = { &aio_write_data, michael@0: michael@0: &pwrite_data, michael@0: &pwrite_NOCANCEL_UNIX2003_data, michael@0: &pwrite_UNIX2003_data, michael@0: &pwrite_NOCANCEL_data, michael@0: michael@0: &write_data, michael@0: &write_NOCANCEL_UNIX2003_data, michael@0: &write_UNIX2003_data, michael@0: &write_NOCANCEL_data, michael@0: michael@0: &writev_data, michael@0: &writev_NOCANCEL_UNIX2003_data, michael@0: &writev_UNIX2003_data, michael@0: &writev_NOCANCEL_data}; michael@0: michael@0: const int NumFunctions = ArrayLength(Functions); michael@0: michael@0: } // anonymous namespace michael@0: michael@0: /******************************** IO Poisoning ********************************/ michael@0: michael@0: namespace mozilla { michael@0: michael@0: void InitPoisonIOInterposer() { michael@0: // Enable reporting from poisoned write methods michael@0: sIsEnabled = true; michael@0: michael@0: // Make sure we only poison writes once! michael@0: static bool WritesArePoisoned = false; michael@0: if (WritesArePoisoned) { michael@0: return; michael@0: } michael@0: WritesArePoisoned = true; michael@0: michael@0: // stdout and stderr are OK. michael@0: MozillaRegisterDebugFD(1); michael@0: MozillaRegisterDebugFD(2); michael@0: michael@0: for (int i = 0; i < NumFunctions; ++i) { michael@0: FuncData *d = Functions[i]; michael@0: if (!d->Function) { michael@0: d->Function = dlsym(RTLD_DEFAULT, d->Name); michael@0: } michael@0: if (!d->Function) { michael@0: continue; michael@0: } michael@0: DebugOnly t = mach_override_ptr(d->Function, d->Wrapper, michael@0: &d->Buffer); michael@0: MOZ_ASSERT(t == err_none); michael@0: } michael@0: } michael@0: michael@0: void OnlyReportDirtyWrites() { michael@0: sOnlyReportDirtyWrites = true; michael@0: } michael@0: michael@0: void ClearPoisonIOInterposer() { michael@0: // Not sure how or if we can unpoison the functions. Would be nice, but no michael@0: // worries we won't need to do this anyway. michael@0: sIsEnabled = false; michael@0: } michael@0: michael@0: } // namespace mozilla