Bug 1833854 - Part 2: Common up GCSchedulingTunables invariant checks r=sfink
[gecko.git] / xpcom / build / PoisonIOInterposerWin.cpp
blob0d78e073ca7fecff6bf425cd445500ae2ee2610c
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 if (::GetModuleHandleW(L"kwsui64.dll")) {
444 return;
447 // Don't poison twice... as this function may only be invoked on the main
448 // thread when no other threads are running, it safe to allow multiple calls
449 // to InitPoisonIOInterposer() without complaining (ie. failing assertions).
450 if (sIOPoisoned) {
451 return;
453 sIOPoisoned = true;
455 MOZ_RELEASE_ASSERT(!sHandleToFilenameCache);
456 sHandleToFilenameCache = mozilla::MakeUnique<HandleToFilenameCache>();
457 mozilla::RunOnShutdown([]() {
458 // The interposer may still be active after the final shutdown phase
459 // (especially since ClearPoisonIOInterposer() is never called, see bug
460 // 1647107), so we cannot just reset the pointer. Instead we put the cache
461 // in shutdown mode, to clear its memory and stop caching operations.
462 sHandleToFilenameCache->Shutdown();
465 // Stdout and Stderr are OK.
466 MozillaRegisterDebugFD(1);
467 if (::GetStdHandle(STD_OUTPUT_HANDLE) != ::GetStdHandle(STD_ERROR_HANDLE)) {
468 MozillaRegisterDebugFD(2);
471 #ifdef MOZ_REPLACE_MALLOC
472 // The contract with InitDebugFd is that the given registry can be used
473 // at any moment, so the instance needs to persist longer than the scope
474 // of this functions.
475 static DebugFdRegistry registry;
476 ReplaceMalloc::InitDebugFd(registry);
477 #endif
479 // Initialize dll interceptor and add hooks
480 sNtDllInterceptor.Init("ntdll.dll");
481 gOriginalNtCreateFile.Set(sNtDllInterceptor, "NtCreateFile",
482 &InterposedNtCreateFile);
483 gOriginalNtReadFile.Set(sNtDllInterceptor, "NtReadFile",
484 &InterposedNtReadFile);
485 gOriginalNtReadFileScatter.Set(sNtDllInterceptor, "NtReadFileScatter",
486 &InterposedNtReadFileScatter);
487 gOriginalNtWriteFile.Set(sNtDllInterceptor, "NtWriteFile",
488 &InterposedNtWriteFile);
489 gOriginalNtWriteFileGather.Set(sNtDllInterceptor, "NtWriteFileGather",
490 &InterposedNtWriteFileGather);
491 gOriginalNtFlushBuffersFile.Set(sNtDllInterceptor, "NtFlushBuffersFile",
492 &InterposedNtFlushBuffersFile);
493 gOriginalNtQueryFullAttributesFile.Set(sNtDllInterceptor,
494 "NtQueryFullAttributesFile",
495 &InterposedNtQueryFullAttributesFile);
498 void ClearPoisonIOInterposer() {
499 MOZ_ASSERT(false, "Never called! See bug 1647107");
500 if (sIOPoisoned) {
501 // Destroy the DLL interceptor
502 sIOPoisoned = false;
503 sNtDllInterceptor.Clear();
504 sHandleToFilenameCache->Clear();
508 } // namespace mozilla