diff options
Diffstat (limited to 'xpcom/build/PoisonIOInterposerWin.cpp')
-rw-r--r-- | xpcom/build/PoisonIOInterposerWin.cpp | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp new file mode 100644 index 000000000..752743b34 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -0,0 +1,501 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "PoisonIOInterposer.h" + +#include <algorithm> +#include <stdio.h> +#include <vector> + +#include <io.h> +#include <windows.h> +#include <winternl.h> + +#include "mozilla/Assertions.h" +#include "mozilla/FileUtilsWin.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" +#include "nsTArray.h" +#include "nsWindowsDllInterceptor.h" +#include "plstr.h" + +#ifdef MOZ_REPLACE_MALLOC +#include "replace_malloc_bridge.h" +#endif + +using namespace mozilla; + +namespace { + +// Keep track of poisoned state. Notice that there is no reason to lock access +// to this variable as it's only changed in InitPoisonIOInterposer and +// ClearPoisonIOInterposer which may only be called on the main-thread when no +// other threads are running. +static bool sIOPoisoned = false; + +/************************ Internal NT API Declarations ************************/ + +/* + * Function pointer declaration for internal NT routine to create/open files. + * For documentation on the NtCreateFile routine, see MSDN. + */ +typedef NTSTATUS (NTAPI* NtCreateFileFn)( + PHANDLE aFileHandle, + ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, + PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, + ULONG aFileAttributes, + ULONG aShareAccess, + ULONG aCreateDisposition, + ULONG aCreateOptions, + PVOID aEaBuffer, + ULONG aEaLength); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * For documentation on the NtReadFile routine, see ZwReadFile on MSDN. + */ +typedef NTSTATUS (NTAPI* NtReadFileFn)( + HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS (NTAPI* NtReadFileScatterFn)( + HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + FILE_SEGMENT_ELEMENT* aSegments, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN. + */ +typedef NTSTATUS (NTAPI* NtWriteFileFn)( + HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS (NTAPI* NtWriteFileGatherFn)( + HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + FILE_SEGMENT_ELEMENT* aSegments, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to flush to disk. + * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile + * on MSDN. + */ +typedef NTSTATUS (NTAPI* NtFlushBuffersFileFn)( + HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION; +/** + * Function pointer delaration for internal NT routine to query file attributes. + * (equivalent to stat) + */ +typedef NTSTATUS (NTAPI* NtQueryFullAttributesFileFn)( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation); + +/*************************** Auxiliary Declarations ***************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the IOInterposeObserver API. + */ +class WinIOAutoObservation : public IOInterposeObserver::Observation +{ +public: + WinIOAutoObservation(IOInterposeObserver::Operation aOp, + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : IOInterposeObserver::Observation( + aOp, sReference, !IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle))) + , mFileHandle(aFileHandle) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; + } + } + + WinIOAutoObservation(IOInterposeObserver::Operation aOp, nsAString& aFilename) + : IOInterposeObserver::Observation(aOp, sReference) + , mFileHandle(nullptr) + , mHasQueriedFilename(false) + , mFilename(nullptr) + { + if (mShouldReport) { + nsAutoString dosPath; + if (NtPathToDosPath(aFilename, dosPath)) { + mFilename = ToNewUnicode(dosPath); + mHasQueriedFilename = true; + } + mOffset.QuadPart = 0; + } + } + + // Custom implementation of IOInterposeObserver::Observation::Filename + const char16_t* Filename() override; + + ~WinIOAutoObservation() + { + Report(); + if (mFilename) { + MOZ_ASSERT(mHasQueriedFilename); + free(mFilename); + mFilename = nullptr; + } + } + +private: + HANDLE mFileHandle; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + char16_t* mFilename; + static const char* sReference; +}; + +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +const char16_t* +WinIOAutoObservation::Filename() +{ + // If mHasQueriedFilename is true, then filename is already stored in mFilename + if (mHasQueriedFilename) { + return mFilename; + } + + nsAutoString utf16Filename; + if (HandleToFilename(mFileHandle, mOffset, utf16Filename)) { + // Heap allocate with leakable memory + mFilename = ToNewUnicode(utf16Filename); + } + mHasQueriedFilename = true; + + // Return filename + return mFilename; +} + +/*************************** IO Interposing Methods ***************************/ + +// Function pointers to original functions +static NtCreateFileFn gOriginalNtCreateFile; +static NtReadFileFn gOriginalNtReadFile; +static NtReadFileScatterFn gOriginalNtReadFileScatter; +static NtWriteFileFn gOriginalNtWriteFile; +static NtWriteFileGatherFn gOriginalNtWriteFileGather; +static NtFlushBuffersFileFn gOriginalNtFlushBuffersFile; +static NtQueryFullAttributesFileFn gOriginalNtQueryFullAttributesFile; + +static NTSTATUS NTAPI +InterposedNtCreateFile(PHANDLE aFileHandle, + ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, + PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, + ULONG aFileAttributes, + ULONG aShareAccess, + ULONG aCreateDisposition, + ULONG aCreateOptions, + PVOID aEaBuffer, + ULONG aEaLength) +{ + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = + aObjectAttributes ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) : + 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(IOInterposeObserver::OpCreateOrOpen, filename); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtCreateFile); + + // Execute original function + return gOriginalNtCreateFile(aFileHandle, + aDesiredAccess, + aObjectAttributes, + aIoStatusBlock, + aAllocationSize, + aFileAttributes, + aShareAccess, + aCreateDisposition, + aCreateOptions, + aEaBuffer, + aEaLength); +} + +static NTSTATUS NTAPI +InterposedNtReadFile(HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFile); + + // Execute original function + return gOriginalNtReadFile(aFileHandle, + aEvent, + aApc, + aApcCtx, + aIoStatus, + aBuffer, + aLength, + aOffset, + aKey); +} + +static NTSTATUS NTAPI +InterposedNtReadFileScatter(HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + FILE_SEGMENT_ELEMENT* aSegments, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFileScatter); + + // Execute original function + return gOriginalNtReadFileScatter(aFileHandle, + aEvent, + aApc, + aApcCtx, + aIoStatus, + aSegments, + aLength, + aOffset, + aKey); +} + +// Interposed NtWriteFile function +static NTSTATUS NTAPI +InterposedNtWriteFile(HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFile); + + // Execute original function + return gOriginalNtWriteFile(aFileHandle, + aEvent, + aApc, + aApcCtx, + aIoStatus, + aBuffer, + aLength, + aOffset, + aKey); +} + +// Interposed NtWriteFileGather function +static NTSTATUS NTAPI +InterposedNtWriteFileGather(HANDLE aFileHandle, + HANDLE aEvent, + PIO_APC_ROUTINE aApc, + PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + FILE_SEGMENT_ELEMENT* aSegments, + ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFileGather); + + // Execute original function + return gOriginalNtWriteFileGather(aFileHandle, + aEvent, + aApc, + aApcCtx, + aIoStatus, + aSegments, + aLength, + aOffset, + aKey); +} + +static NTSTATUS NTAPI +InterposedNtFlushBuffersFile(HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock) +{ + // Report IO + WinIOAutoObservation timer(IOInterposeObserver::OpFSync, aFileHandle, + nullptr); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtFlushBuffersFile); + + // Execute original function + return gOriginalNtFlushBuffersFile(aFileHandle, + aIoStatusBlock); +} + +static NTSTATUS NTAPI +InterposedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation) +{ + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = + aObjectAttributes ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) : + 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(IOInterposeObserver::OpStat, filename); + + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); + + // Execute original function + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); +} + +} // namespace + +/******************************** IO Poisoning ********************************/ + +// Windows DLL interceptor +static WindowsDllInterceptor sNtDllInterceptor; + +namespace mozilla { + +void +InitPoisonIOInterposer() +{ + // Don't poison twice... as this function may only be invoked on the main + // thread when no other threads are running, it safe to allow multiple calls + // to InitPoisonIOInterposer() without complaining (ie. failing assertions). + if (sIOPoisoned) { + return; + } + sIOPoisoned = true; + + // Stdout and Stderr are OK. + MozillaRegisterDebugFD(1); + MozillaRegisterDebugFD(2); + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + // Initialize dll interceptor and add hooks + sNtDllInterceptor.Init("ntdll.dll"); + sNtDllInterceptor.AddHook( + "NtCreateFile", + reinterpret_cast<intptr_t>(InterposedNtCreateFile), + reinterpret_cast<void**>(&gOriginalNtCreateFile)); + sNtDllInterceptor.AddHook( + "NtReadFile", + reinterpret_cast<intptr_t>(InterposedNtReadFile), + reinterpret_cast<void**>(&gOriginalNtReadFile)); + sNtDllInterceptor.AddHook( + "NtReadFileScatter", + reinterpret_cast<intptr_t>(InterposedNtReadFileScatter), + reinterpret_cast<void**>(&gOriginalNtReadFileScatter)); + sNtDllInterceptor.AddHook( + "NtWriteFile", + reinterpret_cast<intptr_t>(InterposedNtWriteFile), + reinterpret_cast<void**>(&gOriginalNtWriteFile)); + sNtDllInterceptor.AddHook( + "NtWriteFileGather", + reinterpret_cast<intptr_t>(InterposedNtWriteFileGather), + reinterpret_cast<void**>(&gOriginalNtWriteFileGather)); + sNtDllInterceptor.AddHook( + "NtFlushBuffersFile", + reinterpret_cast<intptr_t>(InterposedNtFlushBuffersFile), + reinterpret_cast<void**>(&gOriginalNtFlushBuffersFile)); + sNtDllInterceptor.AddHook( + "NtQueryFullAttributesFile", + reinterpret_cast<intptr_t>(InterposedNtQueryFullAttributesFile), + reinterpret_cast<void**>(&gOriginalNtQueryFullAttributesFile)); +} + +void +ClearPoisonIOInterposer() +{ + MOZ_ASSERT(false); + if (sIOPoisoned) { + // Destroy the DLL interceptor + sIOPoisoned = false; + sNtDllInterceptor = WindowsDllInterceptor(); + } +} + +} // namespace mozilla |