michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 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: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/FileUtilsWin.h" michael@0: #include "mozilla/IOInterposer.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsTArray.h" michael@0: #include "nsWindowsDllInterceptor.h" michael@0: #include "plstr.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: namespace { michael@0: michael@0: // Keep track of poisoned state. Notice that there is no reason to lock access michael@0: // to this variable as it's only changed in InitPoisonIOInterposer and michael@0: // ClearPoisonIOInterposer which may only be called on the main-thread when no michael@0: // other threads are running. michael@0: static bool sIOPoisoned = false; michael@0: michael@0: /************************ Internal NT API Declarations ************************/ michael@0: michael@0: /* michael@0: * Function pointer declaration for internal NT routine to create/open files. michael@0: * For documentation on the NtCreateFile routine, see MSDN. michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtCreateFileFn)( michael@0: OUT PHANDLE aFileHandle, michael@0: IN ACCESS_MASK aDesiredAccess, michael@0: IN POBJECT_ATTRIBUTES aObjectAttributes, michael@0: OUT PIO_STATUS_BLOCK aIoStatusBlock, michael@0: IN PLARGE_INTEGER aAllocationSize, michael@0: IN ULONG aFileAttributes, michael@0: IN ULONG aShareAccess, michael@0: IN ULONG aCreateDisposition, michael@0: IN ULONG aCreateOptions, michael@0: IN PVOID aEaBuffer, michael@0: IN ULONG aEaLength michael@0: ); michael@0: michael@0: /** michael@0: * Function pointer declaration for internal NT routine to read data from file. michael@0: * For documentation on the NtReadFile routine, see ZwReadFile on MSDN. michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtReadFileFn)( michael@0: IN HANDLE aFileHandle, michael@0: IN HANDLE aEvent, michael@0: IN PIO_APC_ROUTINE aApc, michael@0: IN PVOID aApcCtx, michael@0: OUT PIO_STATUS_BLOCK aIoStatus, michael@0: OUT PVOID aBuffer, michael@0: IN ULONG aLength, michael@0: IN PLARGE_INTEGER aOffset, michael@0: IN PULONG aKey michael@0: ); michael@0: michael@0: /** michael@0: * Function pointer declaration for internal NT routine to read data from file. michael@0: * No documentation exists, see wine sources for details. michael@0: */ michael@0: typedef NTSTATUS (NTAPI* NtReadFileScatterFn)( michael@0: IN HANDLE aFileHandle, michael@0: IN HANDLE aEvent, michael@0: IN PIO_APC_ROUTINE aApc, michael@0: IN PVOID aApcCtx, michael@0: OUT PIO_STATUS_BLOCK aIoStatus, michael@0: IN FILE_SEGMENT_ELEMENT* aSegments, michael@0: IN ULONG aLength, michael@0: IN PLARGE_INTEGER aOffset, michael@0: IN PULONG aKey michael@0: ); michael@0: michael@0: /** michael@0: * Function pointer declaration for internal NT routine to write data to file. michael@0: * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN. michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtWriteFileFn)( michael@0: IN HANDLE aFileHandle, michael@0: IN HANDLE aEvent, michael@0: IN PIO_APC_ROUTINE aApc, michael@0: IN PVOID aApcCtx, michael@0: OUT PIO_STATUS_BLOCK aIoStatus, michael@0: IN PVOID aBuffer, michael@0: IN ULONG aLength, michael@0: IN PLARGE_INTEGER aOffset, michael@0: IN PULONG aKey michael@0: ); michael@0: michael@0: /** michael@0: * Function pointer declaration for internal NT routine to write data to file. michael@0: * No documentation exists, see wine sources for details. michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtWriteFileGatherFn)( michael@0: IN HANDLE aFileHandle, michael@0: IN HANDLE aEvent, michael@0: IN PIO_APC_ROUTINE aApc, michael@0: IN PVOID aApcCtx, michael@0: OUT PIO_STATUS_BLOCK aIoStatus, michael@0: IN FILE_SEGMENT_ELEMENT* aSegments, michael@0: IN ULONG aLength, michael@0: IN PLARGE_INTEGER aOffset, michael@0: IN PULONG aKey michael@0: ); michael@0: michael@0: /** michael@0: * Function pointer declaration for internal NT routine to flush to disk. michael@0: * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile michael@0: * on MSDN. michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtFlushBuffersFileFn)( michael@0: IN HANDLE aFileHandle, michael@0: OUT PIO_STATUS_BLOCK aIoStatusBlock michael@0: ); michael@0: michael@0: typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION; michael@0: /** michael@0: * Function pointer delaration for internal NT routine to query file attributes. michael@0: * (equivalent to stat) michael@0: */ michael@0: typedef NTSTATUS (NTAPI *NtQueryFullAttributesFileFn)( michael@0: IN POBJECT_ATTRIBUTES aObjectAttributes, michael@0: OUT PFILE_NETWORK_OPEN_INFORMATION aFileInformation michael@0: ); michael@0: michael@0: /*************************** Auxiliary Declarations ***************************/ 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 WinIOAutoObservation : public IOInterposeObserver::Observation michael@0: { michael@0: public: michael@0: WinIOAutoObservation(IOInterposeObserver::Operation aOp, michael@0: HANDLE aFileHandle, const LARGE_INTEGER* aOffset) michael@0: : IOInterposeObserver::Observation(aOp, sReference, michael@0: !IsDebugFile(reinterpret_cast( michael@0: aFileHandle))) michael@0: , mFileHandle(aFileHandle) michael@0: , mHasQueriedFilename(false) michael@0: , mFilename(nullptr) michael@0: { michael@0: if (mShouldReport) { michael@0: mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; michael@0: } michael@0: } michael@0: michael@0: WinIOAutoObservation(IOInterposeObserver::Operation aOp, nsAString& aFilename) michael@0: : IOInterposeObserver::Observation(aOp, sReference) michael@0: , mFileHandle(nullptr) michael@0: , mHasQueriedFilename(false) michael@0: , mFilename(nullptr) michael@0: { michael@0: if (mShouldReport) { michael@0: nsAutoString dosPath; michael@0: if (NtPathToDosPath(aFilename, dosPath)) { michael@0: mFilename = ToNewUnicode(dosPath); michael@0: mHasQueriedFilename = true; michael@0: } michael@0: mOffset.QuadPart = 0; 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: ~WinIOAutoObservation() michael@0: { michael@0: Report(); michael@0: if (mFilename) { michael@0: MOZ_ASSERT(mHasQueriedFilename); michael@0: NS_Free(mFilename); michael@0: mFilename = nullptr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: HANDLE mFileHandle; michael@0: LARGE_INTEGER mOffset; michael@0: bool mHasQueriedFilename; michael@0: char16_t* mFilename; michael@0: static const char* sReference; michael@0: }; michael@0: michael@0: const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; michael@0: michael@0: // Get filename for this observation michael@0: const char16_t* WinIOAutoObservation::Filename() michael@0: { michael@0: // If mHasQueriedFilename is true, then filename is already stored in mFilename michael@0: if (mHasQueriedFilename) { michael@0: return mFilename; michael@0: } michael@0: michael@0: nsAutoString utf16Filename; michael@0: if (HandleToFilename(mFileHandle, mOffset, utf16Filename)) { michael@0: // Heap allocate with leakable memory michael@0: mFilename = ToNewUnicode(utf16Filename); michael@0: } michael@0: mHasQueriedFilename = true; michael@0: michael@0: // Return filename michael@0: return mFilename; michael@0: } michael@0: michael@0: /*************************** IO Interposing Methods ***************************/ michael@0: michael@0: // Function pointers to original functions michael@0: static NtCreateFileFn gOriginalNtCreateFile; michael@0: static NtReadFileFn gOriginalNtReadFile; michael@0: static NtReadFileScatterFn gOriginalNtReadFileScatter; michael@0: static NtWriteFileFn gOriginalNtWriteFile; michael@0: static NtWriteFileGatherFn gOriginalNtWriteFileGather; michael@0: static NtFlushBuffersFileFn gOriginalNtFlushBuffersFile; michael@0: static NtQueryFullAttributesFileFn gOriginalNtQueryFullAttributesFile; michael@0: michael@0: static NTSTATUS NTAPI InterposedNtCreateFile( michael@0: PHANDLE aFileHandle, michael@0: ACCESS_MASK aDesiredAccess, michael@0: POBJECT_ATTRIBUTES aObjectAttributes, michael@0: PIO_STATUS_BLOCK aIoStatusBlock, michael@0: PLARGE_INTEGER aAllocationSize, michael@0: ULONG aFileAttributes, michael@0: ULONG aShareAccess, michael@0: ULONG aCreateDisposition, michael@0: ULONG aCreateOptions, michael@0: PVOID aEaBuffer, michael@0: ULONG aEaLength michael@0: ) michael@0: { michael@0: // Report IO michael@0: const wchar_t* buf = aObjectAttributes ? michael@0: aObjectAttributes->ObjectName->Buffer : michael@0: L""; michael@0: uint32_t len = aObjectAttributes ? michael@0: aObjectAttributes->ObjectName->Length / sizeof(WCHAR) : michael@0: 0; michael@0: nsDependentSubstring filename(buf, len); michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpCreateOrOpen, filename); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtCreateFile); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtCreateFile( michael@0: aFileHandle, michael@0: aDesiredAccess, michael@0: aObjectAttributes, michael@0: aIoStatusBlock, michael@0: aAllocationSize, michael@0: aFileAttributes, michael@0: aShareAccess, michael@0: aCreateDisposition, michael@0: aCreateOptions, michael@0: aEaBuffer, michael@0: aEaLength michael@0: ); michael@0: } michael@0: michael@0: static NTSTATUS NTAPI InterposedNtReadFile( michael@0: HANDLE aFileHandle, michael@0: HANDLE aEvent, michael@0: PIO_APC_ROUTINE aApc, michael@0: PVOID aApcCtx, michael@0: PIO_STATUS_BLOCK aIoStatus, michael@0: PVOID aBuffer, michael@0: ULONG aLength, michael@0: PLARGE_INTEGER aOffset, michael@0: PULONG aKey) michael@0: { michael@0: // Report IO michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtReadFile); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtReadFile( michael@0: aFileHandle, michael@0: aEvent, michael@0: aApc, michael@0: aApcCtx, michael@0: aIoStatus, michael@0: aBuffer, michael@0: aLength, michael@0: aOffset, michael@0: aKey michael@0: ); michael@0: } michael@0: michael@0: static NTSTATUS NTAPI InterposedNtReadFileScatter( michael@0: HANDLE aFileHandle, michael@0: HANDLE aEvent, michael@0: PIO_APC_ROUTINE aApc, michael@0: PVOID aApcCtx, michael@0: PIO_STATUS_BLOCK aIoStatus, michael@0: FILE_SEGMENT_ELEMENT* aSegments, michael@0: ULONG aLength, michael@0: PLARGE_INTEGER aOffset, michael@0: PULONG aKey) michael@0: { michael@0: // Report IO michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpRead, aFileHandle, aOffset); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtReadFileScatter); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtReadFileScatter( michael@0: aFileHandle, michael@0: aEvent, michael@0: aApc, michael@0: aApcCtx, michael@0: aIoStatus, michael@0: aSegments, michael@0: aLength, michael@0: aOffset, michael@0: aKey michael@0: ); michael@0: } michael@0: michael@0: // Interposed NtWriteFile function michael@0: static NTSTATUS NTAPI InterposedNtWriteFile( michael@0: HANDLE aFileHandle, michael@0: HANDLE aEvent, michael@0: PIO_APC_ROUTINE aApc, michael@0: PVOID aApcCtx, michael@0: PIO_STATUS_BLOCK aIoStatus, michael@0: PVOID aBuffer, michael@0: ULONG aLength, michael@0: PLARGE_INTEGER aOffset, michael@0: PULONG aKey) michael@0: { michael@0: // Report IO michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, michael@0: aOffset); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtWriteFile); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtWriteFile( michael@0: aFileHandle, michael@0: aEvent, michael@0: aApc, michael@0: aApcCtx, michael@0: aIoStatus, michael@0: aBuffer, michael@0: aLength, michael@0: aOffset, michael@0: aKey michael@0: ); michael@0: } michael@0: michael@0: // Interposed NtWriteFileGather function michael@0: static NTSTATUS NTAPI InterposedNtWriteFileGather( michael@0: HANDLE aFileHandle, michael@0: HANDLE aEvent, michael@0: PIO_APC_ROUTINE aApc, michael@0: PVOID aApcCtx, michael@0: PIO_STATUS_BLOCK aIoStatus, michael@0: FILE_SEGMENT_ELEMENT* aSegments, michael@0: ULONG aLength, michael@0: PLARGE_INTEGER aOffset, michael@0: PULONG aKey) michael@0: { michael@0: // Report IO michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpWrite, aFileHandle, michael@0: aOffset); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtWriteFileGather); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtWriteFileGather( michael@0: aFileHandle, michael@0: aEvent, michael@0: aApc, michael@0: aApcCtx, michael@0: aIoStatus, michael@0: aSegments, michael@0: aLength, michael@0: aOffset, michael@0: aKey michael@0: ); michael@0: } michael@0: michael@0: static NTSTATUS NTAPI InterposedNtFlushBuffersFile( michael@0: HANDLE aFileHandle, michael@0: PIO_STATUS_BLOCK aIoStatusBlock) michael@0: { michael@0: // Report IO michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpFSync, aFileHandle, michael@0: nullptr); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtFlushBuffersFile); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtFlushBuffersFile( michael@0: aFileHandle, michael@0: aIoStatusBlock michael@0: ); michael@0: } michael@0: michael@0: static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile( michael@0: POBJECT_ATTRIBUTES aObjectAttributes, michael@0: PFILE_NETWORK_OPEN_INFORMATION aFileInformation) michael@0: { michael@0: // Report IO michael@0: const wchar_t* buf = aObjectAttributes ? michael@0: aObjectAttributes->ObjectName->Buffer : michael@0: L""; michael@0: uint32_t len = aObjectAttributes ? michael@0: aObjectAttributes->ObjectName->Length / sizeof(WCHAR) : michael@0: 0; michael@0: nsDependentSubstring filename(buf, len); michael@0: WinIOAutoObservation timer(IOInterposeObserver::OpStat, filename); michael@0: michael@0: // Something is badly wrong if this function is undefined michael@0: MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); michael@0: michael@0: // Execute original function michael@0: return gOriginalNtQueryFullAttributesFile( michael@0: aObjectAttributes, michael@0: aFileInformation michael@0: ); michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: /******************************** IO Poisoning ********************************/ michael@0: michael@0: // Windows DLL interceptor michael@0: static WindowsDllInterceptor sNtDllInterceptor; michael@0: michael@0: namespace mozilla { michael@0: michael@0: void InitPoisonIOInterposer() { michael@0: // Don't poison twice... as this function may only be invoked on the main michael@0: // thread when no other threads are running, it safe to allow multiple calls michael@0: // to InitPoisonIOInterposer() without complaining (ie. failing assertions). michael@0: if (sIOPoisoned) { michael@0: return; michael@0: } michael@0: sIOPoisoned = true; michael@0: michael@0: // Stdout and Stderr are OK. michael@0: MozillaRegisterDebugFD(1); michael@0: MozillaRegisterDebugFD(2); michael@0: michael@0: // Initialize dll interceptor and add hooks michael@0: sNtDllInterceptor.Init("ntdll.dll"); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtCreateFile", michael@0: reinterpret_cast(InterposedNtCreateFile), michael@0: reinterpret_cast(&gOriginalNtCreateFile) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtReadFile", michael@0: reinterpret_cast(InterposedNtReadFile), michael@0: reinterpret_cast(&gOriginalNtReadFile) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtReadFileScatter", michael@0: reinterpret_cast(InterposedNtReadFileScatter), michael@0: reinterpret_cast(&gOriginalNtReadFileScatter) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtWriteFile", michael@0: reinterpret_cast(InterposedNtWriteFile), michael@0: reinterpret_cast(&gOriginalNtWriteFile) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtWriteFileGather", michael@0: reinterpret_cast(InterposedNtWriteFileGather), michael@0: reinterpret_cast(&gOriginalNtWriteFileGather) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtFlushBuffersFile", michael@0: reinterpret_cast(InterposedNtFlushBuffersFile), michael@0: reinterpret_cast(&gOriginalNtFlushBuffersFile) michael@0: ); michael@0: sNtDllInterceptor.AddHook( michael@0: "NtQueryFullAttributesFile", michael@0: reinterpret_cast(InterposedNtQueryFullAttributesFile), michael@0: reinterpret_cast(&gOriginalNtQueryFullAttributesFile) michael@0: ); michael@0: } michael@0: michael@0: void ClearPoisonIOInterposer() { michael@0: MOZ_ASSERT(false); michael@0: if (sIOPoisoned) { michael@0: // Destroy the DLL interceptor michael@0: sIOPoisoned = false; michael@0: sNtDllInterceptor = WindowsDllInterceptor(); michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla