/* -*- 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