1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/xpcom/build/PoisonIOInterposerMac.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,349 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* vim:set ts=4 sw=4 sts=4 ci et: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "PoisonIOInterposer.h" 1.11 +#include "mach_override.h" 1.12 + 1.13 +#include "mozilla/ArrayUtils.h" 1.14 +#include "mozilla/Assertions.h" 1.15 +#include "mozilla/DebugOnly.h" 1.16 +#include "mozilla/IOInterposer.h" 1.17 +#include "mozilla/Mutex.h" 1.18 +#include "mozilla/ProcessedStack.h" 1.19 +#include "mozilla/Scoped.h" 1.20 +#include "mozilla/Telemetry.h" 1.21 +#include "nsPrintfCString.h" 1.22 +#include "nsStackWalk.h" 1.23 +#include "nsTraceRefcnt.h" 1.24 +#include "plstr.h" 1.25 +#include "prio.h" 1.26 + 1.27 +#include <algorithm> 1.28 +#include <vector> 1.29 + 1.30 +#include <sys/param.h> 1.31 +#include <sys/stat.h> 1.32 +#include <sys/socket.h> 1.33 +#include <sys/uio.h> 1.34 +#include <aio.h> 1.35 +#include <dlfcn.h> 1.36 +#include <fcntl.h> 1.37 + 1.38 +namespace { 1.39 + 1.40 +using namespace mozilla; 1.41 + 1.42 +// Bit tracking if poisoned writes are enabled 1.43 +static bool sIsEnabled = false; 1.44 + 1.45 +// Check if writes are dirty before reporting IO 1.46 +static bool sOnlyReportDirtyWrites = false; 1.47 + 1.48 +// Routines for write validation 1.49 +bool IsValidWrite(int fd, const void *wbuf, size_t count); 1.50 +bool IsIPCWrite(int fd, const struct stat &buf); 1.51 + 1.52 +/******************************** IO AutoTimer ********************************/ 1.53 + 1.54 +/** 1.55 + * RAII class for timing the duration of an I/O call and reporting the result 1.56 + * to the IOInterposeObserver API. 1.57 + */ 1.58 +class MacIOAutoObservation : public IOInterposeObserver::Observation 1.59 +{ 1.60 +public: 1.61 + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd) 1.62 + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && 1.63 + !IsDebugFile(aFd)) 1.64 + , mFd(aFd) 1.65 + , mHasQueriedFilename(false) 1.66 + , mFilename(nullptr) 1.67 + { 1.68 + } 1.69 + 1.70 + MacIOAutoObservation(IOInterposeObserver::Operation aOp, int aFd, 1.71 + const void *aBuf, size_t aCount) 1.72 + : IOInterposeObserver::Observation(aOp, sReference, sIsEnabled && 1.73 + !IsDebugFile(aFd) && 1.74 + IsValidWrite(aFd, aBuf, aCount)) 1.75 + , mFd(aFd) 1.76 + , mHasQueriedFilename(false) 1.77 + , mFilename(nullptr) 1.78 + { 1.79 + } 1.80 + 1.81 + // Custom implementation of IOInterposeObserver::Observation::Filename 1.82 + const char16_t* Filename() MOZ_OVERRIDE; 1.83 + 1.84 + ~MacIOAutoObservation() 1.85 + { 1.86 + Report(); 1.87 + if (mFilename) { 1.88 + NS_Free(mFilename); 1.89 + mFilename = nullptr; 1.90 + } 1.91 + } 1.92 + 1.93 +private: 1.94 + int mFd; 1.95 + bool mHasQueriedFilename; 1.96 + char16_t* mFilename; 1.97 + static const char* sReference; 1.98 +}; 1.99 + 1.100 +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; 1.101 + 1.102 +// Get filename for this observation 1.103 +const char16_t* MacIOAutoObservation::Filename() 1.104 +{ 1.105 + // If mHasQueriedFilename is true, then we already have it 1.106 + if (mHasQueriedFilename) { 1.107 + return mFilename; 1.108 + } 1.109 + char filename[MAXPATHLEN]; 1.110 + if (fcntl(mFd, F_GETPATH, filename) != -1) { 1.111 + mFilename = UTF8ToNewUnicode(nsDependentCString(filename)); 1.112 + } else { 1.113 + mFilename = nullptr; 1.114 + } 1.115 + mHasQueriedFilename = true; 1.116 + 1.117 + // Return filename 1.118 + return mFilename; 1.119 +} 1.120 + 1.121 +/****************************** Write Validation ******************************/ 1.122 + 1.123 +// We want to detect "actual" writes, not IPC. Some IPC mechanisms are 1.124 +// implemented with file descriptors, so filter them out. 1.125 +bool IsIPCWrite(int fd, const struct stat &buf) { 1.126 + if ((buf.st_mode & S_IFMT) == S_IFIFO) { 1.127 + return true; 1.128 + } 1.129 + 1.130 + if ((buf.st_mode & S_IFMT) != S_IFSOCK) { 1.131 + return false; 1.132 + } 1.133 + 1.134 + sockaddr_storage address; 1.135 + socklen_t len = sizeof(address); 1.136 + if (getsockname(fd, (sockaddr*) &address, &len) != 0) { 1.137 + return true; // Ignore the fd if we can't find what it is. 1.138 + } 1.139 + 1.140 + return address.ss_family == AF_UNIX; 1.141 +} 1.142 + 1.143 +// We want to report actual disk IO not things that don't move bits on the disk 1.144 +bool IsValidWrite(int fd, const void *wbuf, size_t count) 1.145 +{ 1.146 + // Ignore writes of zero bytes, Firefox does some during shutdown. 1.147 + if (count == 0) { 1.148 + return false; 1.149 + } 1.150 + 1.151 + { 1.152 + struct stat buf; 1.153 + int rv = fstat(fd, &buf); 1.154 + if (rv != 0) { 1.155 + return true; 1.156 + } 1.157 + 1.158 + if (IsIPCWrite(fd, buf)) { 1.159 + return false; 1.160 + } 1.161 + } 1.162 + 1.163 + // For writev we pass a nullptr wbuf. We should only get here from 1.164 + // dbm, and it uses write, so assert that we have wbuf. 1.165 + if (!wbuf) { 1.166 + return true; 1.167 + } 1.168 + 1.169 + // Break, here if we're allowed to report non-dirty writes 1.170 + if(!sOnlyReportDirtyWrites) { 1.171 + return true; 1.172 + } 1.173 + 1.174 + // As a really bad hack, accept writes that don't change the on disk 1.175 + // content. This is needed because dbm doesn't keep track of dirty bits 1.176 + // and can end up writing the same data to disk twice. Once when the 1.177 + // user (nss) asks it to sync and once when closing the database. 1.178 + ScopedFreePtr<void> wbuf2(malloc(count)); 1.179 + if (!wbuf2) { 1.180 + return true; 1.181 + } 1.182 + off_t pos = lseek(fd, 0, SEEK_CUR); 1.183 + if (pos == -1) { 1.184 + return true; 1.185 + } 1.186 + ssize_t r = read(fd, wbuf2, count); 1.187 + if (r < 0 || (size_t)r != count) { 1.188 + return true; 1.189 + } 1.190 + int cmp = memcmp(wbuf, wbuf2, count); 1.191 + if (cmp != 0) { 1.192 + return true; 1.193 + } 1.194 + off_t pos2 = lseek(fd, pos, SEEK_SET); 1.195 + if (pos2 != pos) { 1.196 + return true; 1.197 + } 1.198 + 1.199 + // Otherwise this is not a valid write 1.200 + return false; 1.201 +} 1.202 + 1.203 +/*************************** Function Interception ***************************/ 1.204 + 1.205 +/** Structure for declaration of function override */ 1.206 +struct FuncData { 1.207 + const char *Name; // Name of the function for the ones we use dlsym 1.208 + const void *Wrapper; // The function that we will replace 'Function' with 1.209 + void *Function; // The function that will be replaced with 'Wrapper' 1.210 + void *Buffer; // Will point to the jump buffer that lets us call 1.211 + // 'Function' after it has been replaced. 1.212 +}; 1.213 + 1.214 +// Wrap aio_write. We have not seen it before, so just assert/report it. 1.215 +typedef ssize_t (*aio_write_t)(struct aiocb *aiocbp); 1.216 +ssize_t wrap_aio_write(struct aiocb *aiocbp); 1.217 +FuncData aio_write_data = { 0, (void*) wrap_aio_write, (void*) aio_write }; 1.218 +ssize_t wrap_aio_write(struct aiocb *aiocbp) { 1.219 + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, aiocbp->aio_fildes); 1.220 + 1.221 + aio_write_t old_write = (aio_write_t) aio_write_data.Buffer; 1.222 + return old_write(aiocbp); 1.223 +} 1.224 + 1.225 +// Wrap pwrite-like functions. 1.226 +// We have not seen them before, so just assert/report it. 1.227 +typedef ssize_t (*pwrite_t)(int fd, const void *buf, size_t nbyte, off_t offset); 1.228 +template<FuncData &foo> 1.229 +ssize_t wrap_pwrite_temp(int fd, const void *buf, size_t nbyte, off_t offset) { 1.230 + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd); 1.231 + pwrite_t old_write = (pwrite_t) foo.Buffer; 1.232 + return old_write(fd, buf, nbyte, offset); 1.233 +} 1.234 + 1.235 +// Define a FuncData for a pwrite-like functions. 1.236 +#define DEFINE_PWRITE_DATA(X, NAME) \ 1.237 +FuncData X ## _data = { NAME, (void*) wrap_pwrite_temp<X ## _data> }; \ 1.238 + 1.239 +// This exists everywhere. 1.240 +DEFINE_PWRITE_DATA(pwrite, "pwrite") 1.241 +// These exist on 32 bit OS X 1.242 +DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003"); 1.243 +DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003"); 1.244 +// This exists on 64 bit OS X 1.245 +DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); 1.246 + 1.247 + 1.248 +typedef ssize_t (*writev_t)(int fd, const struct iovec *iov, int iovcnt); 1.249 +template<FuncData &foo> 1.250 +ssize_t wrap_writev_temp(int fd, const struct iovec *iov, int iovcnt) { 1.251 + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, nullptr, iovcnt); 1.252 + writev_t old_write = (writev_t) foo.Buffer; 1.253 + return old_write(fd, iov, iovcnt); 1.254 +} 1.255 + 1.256 +// Define a FuncData for a writev-like functions. 1.257 +#define DEFINE_WRITEV_DATA(X, NAME) \ 1.258 +FuncData X ## _data = { NAME, (void*) wrap_writev_temp<X ## _data> }; \ 1.259 + 1.260 +// This exists everywhere. 1.261 +DEFINE_WRITEV_DATA(writev, "writev"); 1.262 +// These exist on 32 bit OS X 1.263 +DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003"); 1.264 +DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003"); 1.265 +// This exists on 64 bit OS X 1.266 +DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); 1.267 + 1.268 +typedef ssize_t (*write_t)(int fd, const void *buf, size_t count); 1.269 +template<FuncData &foo> 1.270 +ssize_t wrap_write_temp(int fd, const void *buf, size_t count) { 1.271 + MacIOAutoObservation timer(IOInterposeObserver::OpWrite, fd, buf, count); 1.272 + write_t old_write = (write_t) foo.Buffer; 1.273 + return old_write(fd, buf, count); 1.274 +} 1.275 + 1.276 +// Define a FuncData for a write-like functions. 1.277 +#define DEFINE_WRITE_DATA(X, NAME) \ 1.278 +FuncData X ## _data = { NAME, (void*) wrap_write_temp<X ## _data> }; \ 1.279 + 1.280 +// This exists everywhere. 1.281 +DEFINE_WRITE_DATA(write, "write"); 1.282 +// These exist on 32 bit OS X 1.283 +DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003"); 1.284 +DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003"); 1.285 +// This exists on 64 bit OS X 1.286 +DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL"); 1.287 + 1.288 +FuncData *Functions[] = { &aio_write_data, 1.289 + 1.290 + &pwrite_data, 1.291 + &pwrite_NOCANCEL_UNIX2003_data, 1.292 + &pwrite_UNIX2003_data, 1.293 + &pwrite_NOCANCEL_data, 1.294 + 1.295 + &write_data, 1.296 + &write_NOCANCEL_UNIX2003_data, 1.297 + &write_UNIX2003_data, 1.298 + &write_NOCANCEL_data, 1.299 + 1.300 + &writev_data, 1.301 + &writev_NOCANCEL_UNIX2003_data, 1.302 + &writev_UNIX2003_data, 1.303 + &writev_NOCANCEL_data}; 1.304 + 1.305 +const int NumFunctions = ArrayLength(Functions); 1.306 + 1.307 +} // anonymous namespace 1.308 + 1.309 +/******************************** IO Poisoning ********************************/ 1.310 + 1.311 +namespace mozilla { 1.312 + 1.313 +void InitPoisonIOInterposer() { 1.314 + // Enable reporting from poisoned write methods 1.315 + sIsEnabled = true; 1.316 + 1.317 + // Make sure we only poison writes once! 1.318 + static bool WritesArePoisoned = false; 1.319 + if (WritesArePoisoned) { 1.320 + return; 1.321 + } 1.322 + WritesArePoisoned = true; 1.323 + 1.324 + // stdout and stderr are OK. 1.325 + MozillaRegisterDebugFD(1); 1.326 + MozillaRegisterDebugFD(2); 1.327 + 1.328 + for (int i = 0; i < NumFunctions; ++i) { 1.329 + FuncData *d = Functions[i]; 1.330 + if (!d->Function) { 1.331 + d->Function = dlsym(RTLD_DEFAULT, d->Name); 1.332 + } 1.333 + if (!d->Function) { 1.334 + continue; 1.335 + } 1.336 + DebugOnly<mach_error_t> t = mach_override_ptr(d->Function, d->Wrapper, 1.337 + &d->Buffer); 1.338 + MOZ_ASSERT(t == err_none); 1.339 + } 1.340 +} 1.341 + 1.342 +void OnlyReportDirtyWrites() { 1.343 + sOnlyReportDirtyWrites = true; 1.344 +} 1.345 + 1.346 +void ClearPoisonIOInterposer() { 1.347 + // Not sure how or if we can unpoison the functions. Would be nice, but no 1.348 + // worries we won't need to do this anyway. 1.349 + sIsEnabled = false; 1.350 +} 1.351 + 1.352 +} // namespace mozilla