Bug 1869043 assert that graph set access is main thread only r=padenot
[gecko.git] / xpcom / build / PoisonIOInterposerWin.cpp
blobad9a11dbb17f0b3d9a4bc61289576ce7a6c8ff93
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PoisonIOInterposer.h"
9 #include <algorithm>
10 #include <stdio.h>
11 #include <vector>
13 #include <io.h>
14 #include <windows.h>
15 #include <winternl.h>
17 #include "mozilla/Assertions.h"
18 #include "mozilla/ClearOnShutdown.h"
19 #include "mozilla/FileUtilsWin.h"
20 #include "mozilla/IOInterposer.h"
21 #include "mozilla/Mutex.h"
22 #include "mozilla/NativeNt.h"
23 #include "mozilla/SmallArrayLRUCache.h"
24 #include "mozilla/TimeStamp.h"
25 #include "mozilla/UniquePtr.h"
26 #include "nsTArray.h"
27 #include "nsWindowsDllInterceptor.h"
29 #ifdef MOZ_REPLACE_MALLOC
30 # include "replace_malloc_bridge.h"
31 #endif
33 namespace {
35 // Keep track of poisoned state. Notice that there is no reason to lock access
36 // to this variable as it's only changed in InitPoisonIOInterposer and
37 // ClearPoisonIOInterposer which may only be called on the main-thread when no
38 // other threads are running.
39 static bool sIOPoisoned = false;
41 /************************ Internal NT API Declarations ************************/
44 * Function pointer declaration for internal NT routine to create/open files.
45 * For documentation on the NtCreateFile routine, see MSDN.
47 typedef NTSTATUS(NTAPI* NtCreateFileFn)(
48 PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess,
49 POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock,
50 PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess,
51 ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer,
52 ULONG aEaLength);
54 /**
55 * Function pointer declaration for internal NT routine to read data from file.
56 * For documentation on the NtReadFile routine, see ZwReadFile on MSDN.
58 typedef NTSTATUS(NTAPI* NtReadFileFn)(HANDLE aFileHandle, HANDLE aEvent,
59 PIO_APC_ROUTINE aApc, PVOID aApcCtx,
60 PIO_STATUS_BLOCK aIoStatus, PVOID aBuffer,
61 ULONG aLength, PLARGE_INTEGER aOffset,
62 PULONG aKey);
64 /**
65 * Function pointer declaration for internal NT routine to read data from file.
66 * No documentation exists, see wine sources for details.
68 typedef NTSTATUS(NTAPI* NtReadFileScatterFn)(
69 HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
70 PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
71 PLARGE_INTEGER aOffset, PULONG aKey);
73 /**
74 * Function pointer declaration for internal NT routine to write data to file.
75 * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN.
77 typedef NTSTATUS(NTAPI* NtWriteFileFn)(HANDLE aFileHandle, HANDLE aEvent,
78 PIO_APC_ROUTINE aApc, PVOID aApcCtx,
79 PIO_STATUS_BLOCK aIoStatus,
80 PVOID aBuffer, ULONG aLength,
81 PLARGE_INTEGER aOffset, PULONG aKey);
83 /**
84 * Function pointer declaration for internal NT routine to write data to file.
85 * No documentation exists, see wine sources for details.
87 typedef NTSTATUS(NTAPI* NtWriteFileGatherFn)(
88 HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
89 PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
90 PLARGE_INTEGER aOffset, PULONG aKey);
92 /**
93 * Function pointer declaration for internal NT routine to flush to disk.
94 * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile
95 * on MSDN.
97 typedef NTSTATUS(NTAPI* NtFlushBuffersFileFn)(HANDLE aFileHandle,
98 PIO_STATUS_BLOCK aIoStatusBlock);
100 typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION;
102 * Function pointer delaration for internal NT routine to query file attributes.
103 * (equivalent to stat)
105 typedef NTSTATUS(NTAPI* NtQueryFullAttributesFileFn)(
106 POBJECT_ATTRIBUTES aObjectAttributes,
107 PFILE_NETWORK_OPEN_INFORMATION aFileInformation);
109 /*************************** Auxiliary Declarations ***************************/
111 // Cache of filenames associated with handles.
112 // `static` to be shared between all calls to `Filename()`.
113 // This assumes handles are not reused, at least within a windows of 32
114 // handles.
115 // Profiling showed that during startup, around half of `Filename()` calls are
116 // resolved with the first entry (best case), and 32 entries cover >95% of
117 // cases, reducing the average `Filename()` cost by 5-10x.
118 using HandleToFilenameCache = mozilla::SmallArrayLRUCache<HANDLE, nsString, 32>;
119 static mozilla::UniquePtr<HandleToFilenameCache> sHandleToFilenameCache;
122 * RAII class for timing the duration of an I/O call and reporting the result
123 * to the mozilla::IOInterposeObserver API.
125 class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation {
126 public:
127 WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,
128 HANDLE aFileHandle, const LARGE_INTEGER* aOffset)
129 : mozilla::IOInterposeObserver::Observation(
130 aOp, sReference,
131 !mozilla::IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle))),
132 mFileHandle(aFileHandle),
133 mFileHandleType(GetFileType(aFileHandle)),
134 mHasQueriedFilename(false) {
135 if (mShouldReport) {
136 mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0;
140 WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp,
141 nsAString& aFilename)
142 : mozilla::IOInterposeObserver::Observation(aOp, sReference),
143 mFileHandle(nullptr),
144 mFileHandleType(FILE_TYPE_UNKNOWN),
145 mHasQueriedFilename(false) {
146 if (mShouldReport) {
147 nsAutoString dosPath;
148 if (mozilla::NtPathToDosPath(aFilename, dosPath)) {
149 mFilename = dosPath;
150 } else {
151 // If we can't get a dosPath, what we have is better than nothing.
152 mFilename = aFilename;
154 mHasQueriedFilename = true;
155 mOffset.QuadPart = 0;
159 void SetHandle(HANDLE aFileHandle) {
160 mFileHandle = aFileHandle;
161 if (aFileHandle) {
162 // Note: `GetFileType()` is fast enough that we don't need to cache it.
163 mFileHandleType = GetFileType(aFileHandle);
165 if (mHasQueriedFilename) {
166 // `mHasQueriedFilename` indicates we already have a filename, add it to
167 // the cache with the now-known handle.
168 sHandleToFilenameCache->Add(aFileHandle, mFilename);
173 const char* FileType() const override;
175 void Filename(nsAString& aFilename) override;
177 ~WinIOAutoObservation() { Report(); }
179 private:
180 HANDLE mFileHandle;
181 DWORD mFileHandleType;
182 LARGE_INTEGER mOffset;
183 bool mHasQueriedFilename;
184 nsString mFilename;
185 static const char* sReference;
188 const char* WinIOAutoObservation::sReference = "PoisonIOInterposer";
190 // Get filename for this observation
191 void WinIOAutoObservation::Filename(nsAString& aFilename) {
192 // If mHasQueriedFilename is true, then filename is already stored in
193 // mFilename
194 if (mHasQueriedFilename) {
195 aFilename = mFilename;
196 return;
199 if (mFileHandle) {
200 mFilename = sHandleToFilenameCache->FetchOrAdd(mFileHandle, [&]() {
201 nsString filename;
202 if (!mozilla::HandleToFilename(mFileHandle, mOffset, filename)) {
203 // HandleToFilename could fail (return false) but still have added
204 // something to `filename`, so it should be cleared in this case.
205 filename.Truncate();
207 return filename;
210 mHasQueriedFilename = true;
212 aFilename = mFilename;
215 const char* WinIOAutoObservation::FileType() const {
216 if (mFileHandle) {
217 switch (mFileHandleType) {
218 case FILE_TYPE_CHAR:
219 return "Char";
220 case FILE_TYPE_DISK:
221 return "File";
222 case FILE_TYPE_PIPE:
223 return "Pipe";
224 case FILE_TYPE_REMOTE:
225 return "Remote";
226 case FILE_TYPE_UNKNOWN:
227 default:
228 break;
231 // Fallback to base class default implementation.
232 return mozilla::IOInterposeObserver::Observation::FileType();
235 /*************************** IO Interposing Methods ***************************/
237 // Function pointers to original functions
238 static mozilla::WindowsDllInterceptor::FuncHookType<NtCreateFileFn>
239 gOriginalNtCreateFile;
240 static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileFn>
241 gOriginalNtReadFile;
242 static mozilla::WindowsDllInterceptor::FuncHookType<NtReadFileScatterFn>
243 gOriginalNtReadFileScatter;
244 static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileFn>
245 gOriginalNtWriteFile;
246 static mozilla::WindowsDllInterceptor::FuncHookType<NtWriteFileGatherFn>
247 gOriginalNtWriteFileGather;
248 static mozilla::WindowsDllInterceptor::FuncHookType<NtFlushBuffersFileFn>
249 gOriginalNtFlushBuffersFile;
250 static mozilla::WindowsDllInterceptor::FuncHookType<NtQueryFullAttributesFileFn>
251 gOriginalNtQueryFullAttributesFile;
253 static NTSTATUS NTAPI InterposedNtCreateFile(
254 PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess,
255 POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock,
256 PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess,
257 ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer,
258 ULONG aEaLength) {
259 // Something is badly wrong if this function is undefined
260 MOZ_ASSERT(gOriginalNtCreateFile);
262 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
263 return gOriginalNtCreateFile(
264 aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock,
265 aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition,
266 aCreateOptions, aEaBuffer, aEaLength);
269 // Report IO
270 const wchar_t* buf =
271 aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
272 uint32_t len = aObjectAttributes
273 ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR)
274 : 0;
275 nsDependentSubstring filename(buf, len);
276 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen,
277 filename);
279 // Execute original function
280 NTSTATUS status = gOriginalNtCreateFile(
281 aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock,
282 aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition,
283 aCreateOptions, aEaBuffer, aEaLength);
284 if (NT_SUCCESS(status) && aFileHandle) {
285 timer.SetHandle(*aFileHandle);
287 return status;
290 static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent,
291 PIO_APC_ROUTINE aApc, PVOID aApcCtx,
292 PIO_STATUS_BLOCK aIoStatus,
293 PVOID aBuffer, ULONG aLength,
294 PLARGE_INTEGER aOffset,
295 PULONG aKey) {
296 // Something is badly wrong if this function is undefined
297 MOZ_ASSERT(gOriginalNtReadFile);
299 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
300 return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
301 aBuffer, aLength, aOffset, aKey);
304 // Report IO
305 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
306 aOffset);
308 // Execute original function
309 return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
310 aBuffer, aLength, aOffset, aKey);
313 static NTSTATUS NTAPI InterposedNtReadFileScatter(
314 HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
315 PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
316 PLARGE_INTEGER aOffset, PULONG aKey) {
317 // Something is badly wrong if this function is undefined
318 MOZ_ASSERT(gOriginalNtReadFileScatter);
320 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
321 return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx,
322 aIoStatus, aSegments, aLength, aOffset,
323 aKey);
326 // Report IO
327 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle,
328 aOffset);
330 // Execute original function
331 return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx,
332 aIoStatus, aSegments, aLength, aOffset,
333 aKey);
336 // Interposed NtWriteFile function
337 static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent,
338 PIO_APC_ROUTINE aApc, PVOID aApcCtx,
339 PIO_STATUS_BLOCK aIoStatus,
340 PVOID aBuffer, ULONG aLength,
341 PLARGE_INTEGER aOffset,
342 PULONG aKey) {
343 // Something is badly wrong if this function is undefined
344 MOZ_ASSERT(gOriginalNtWriteFile);
346 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
347 return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
348 aBuffer, aLength, aOffset, aKey);
351 // Report IO
352 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
353 aOffset);
355 // Execute original function
356 return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus,
357 aBuffer, aLength, aOffset, aKey);
360 // Interposed NtWriteFileGather function
361 static NTSTATUS NTAPI InterposedNtWriteFileGather(
362 HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx,
363 PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength,
364 PLARGE_INTEGER aOffset, PULONG aKey) {
365 // Something is badly wrong if this function is undefined
366 MOZ_ASSERT(gOriginalNtWriteFileGather);
368 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
369 return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx,
370 aIoStatus, aSegments, aLength, aOffset,
371 aKey);
374 // Report IO
375 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle,
376 aOffset);
378 // Execute original function
379 return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx,
380 aIoStatus, aSegments, aLength, aOffset,
381 aKey);
384 static NTSTATUS NTAPI InterposedNtFlushBuffersFile(
385 HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) {
386 // Something is badly wrong if this function is undefined
387 MOZ_ASSERT(gOriginalNtFlushBuffersFile);
389 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
390 return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock);
393 // Report IO
394 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle,
395 nullptr);
397 // Execute original function
398 return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock);
401 static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile(
402 POBJECT_ATTRIBUTES aObjectAttributes,
403 PFILE_NETWORK_OPEN_INFORMATION aFileInformation) {
404 // Something is badly wrong if this function is undefined
405 MOZ_ASSERT(gOriginalNtQueryFullAttributesFile);
407 if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) {
408 return gOriginalNtQueryFullAttributesFile(aObjectAttributes,
409 aFileInformation);
412 // Report IO
413 const wchar_t* buf =
414 aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L"";
415 uint32_t len = aObjectAttributes
416 ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR)
417 : 0;
418 nsDependentSubstring filename(buf, len);
419 WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename);
421 // Execute original function
422 return gOriginalNtQueryFullAttributesFile(aObjectAttributes,
423 aFileInformation);
426 } // namespace
428 /******************************** IO Poisoning ********************************/
430 // Windows DLL interceptor
431 static mozilla::WindowsDllInterceptor sNtDllInterceptor;
433 namespace mozilla {
435 void InitPoisonIOInterposer() {
436 // Currently we hook the functions not early enough to precede third-party
437 // injections. Until we implement a compatible way e.g. applying a hook
438 // in the parent process (bug 1646804), we skip interposing functions under
439 // the known condition(s).
441 // Bug 1679741: Kingsoft Internet Security calls NtReadFile in their thread
442 // simultaneously when we're applying a hook on NtReadFile.
443 // Bug 1705042: Symantec applies its own hook on NtReadFile, and ends up
444 // overwriting part of ours in an incompatible way.
445 if (::GetModuleHandleW(L"kwsui64.dll") || ::GetModuleHandleW(L"ffm64.dll")) {
446 return;
449 // Don't poison twice... as this function may only be invoked on the main
450 // thread when no other threads are running, it safe to allow multiple calls
451 // to InitPoisonIOInterposer() without complaining (ie. failing assertions).
452 if (sIOPoisoned) {
453 return;
455 sIOPoisoned = true;
457 MOZ_RELEASE_ASSERT(!sHandleToFilenameCache);
458 sHandleToFilenameCache = mozilla::MakeUnique<HandleToFilenameCache>();
459 mozilla::RunOnShutdown([]() {
460 // The interposer may still be active after the final shutdown phase
461 // (especially since ClearPoisonIOInterposer() is never called, see bug
462 // 1647107), so we cannot just reset the pointer. Instead we put the cache
463 // in shutdown mode, to clear its memory and stop caching operations.
464 sHandleToFilenameCache->Shutdown();
467 // Stdout and Stderr are OK.
468 MozillaRegisterDebugFD(1);
469 if (::GetStdHandle(STD_OUTPUT_HANDLE) != ::GetStdHandle(STD_ERROR_HANDLE)) {
470 MozillaRegisterDebugFD(2);
473 #ifdef MOZ_REPLACE_MALLOC
474 // The contract with InitDebugFd is that the given registry can be used
475 // at any moment, so the instance needs to persist longer than the scope
476 // of this functions.
477 static DebugFdRegistry registry;
478 ReplaceMalloc::InitDebugFd(registry);
479 #endif
481 // Initialize dll interceptor and add hooks
482 sNtDllInterceptor.Init("ntdll.dll");
483 gOriginalNtCreateFile.Set(sNtDllInterceptor, "NtCreateFile",
484 &InterposedNtCreateFile);
485 gOriginalNtReadFile.Set(sNtDllInterceptor, "NtReadFile",
486 &InterposedNtReadFile);
487 gOriginalNtReadFileScatter.Set(sNtDllInterceptor, "NtReadFileScatter",
488 &InterposedNtReadFileScatter);
489 gOriginalNtWriteFile.Set(sNtDllInterceptor, "NtWriteFile",
490 &InterposedNtWriteFile);
491 gOriginalNtWriteFileGather.Set(sNtDllInterceptor, "NtWriteFileGather",
492 &InterposedNtWriteFileGather);
493 gOriginalNtFlushBuffersFile.Set(sNtDllInterceptor, "NtFlushBuffersFile",
494 &InterposedNtFlushBuffersFile);
495 gOriginalNtQueryFullAttributesFile.Set(sNtDllInterceptor,
496 "NtQueryFullAttributesFile",
497 &InterposedNtQueryFullAttributesFile);
500 void ClearPoisonIOInterposer() {
501 MOZ_ASSERT(false, "Never called! See bug 1647107");
502 if (sIOPoisoned) {
503 // Destroy the DLL interceptor
504 sIOPoisoned = false;
505 sNtDllInterceptor.Clear();
506 sHandleToFilenameCache->Clear();
510 } // namespace mozilla