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"
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"
27 #include "nsWindowsDllInterceptor.h"
29 #ifdef MOZ_REPLACE_MALLOC
30 # include "replace_malloc_bridge.h"
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
,
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
,
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
);
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
);
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
);
93 * Function pointer declaration for internal NT routine to flush to disk.
94 * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile
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
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
{
127 WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp
,
128 HANDLE aFileHandle
, const LARGE_INTEGER
* aOffset
)
129 : mozilla::IOInterposeObserver::Observation(
131 !mozilla::IsDebugFile(reinterpret_cast<intptr_t>(aFileHandle
))),
132 mFileHandle(aFileHandle
),
133 mFileHandleType(GetFileType(aFileHandle
)),
134 mHasQueriedFilename(false) {
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) {
147 nsAutoString dosPath
;
148 if (mozilla::NtPathToDosPath(aFilename
, dosPath
)) {
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
;
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(); }
181 DWORD mFileHandleType
;
182 LARGE_INTEGER mOffset
;
183 bool mHasQueriedFilename
;
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
194 if (mHasQueriedFilename
) {
195 aFilename
= mFilename
;
200 mFilename
= sHandleToFilenameCache
->FetchOrAdd(mFileHandle
, [&]() {
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.
210 mHasQueriedFilename
= true;
212 aFilename
= mFilename
;
215 const char* WinIOAutoObservation::FileType() const {
217 switch (mFileHandleType
) {
224 case FILE_TYPE_REMOTE
:
226 case FILE_TYPE_UNKNOWN
:
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
>
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
,
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
);
271 aObjectAttributes
? aObjectAttributes
->ObjectName
->Buffer
: L
"";
272 uint32_t len
= aObjectAttributes
273 ? aObjectAttributes
->ObjectName
->Length
/ sizeof(WCHAR
)
275 nsDependentSubstring
filename(buf
, len
);
276 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpCreateOrOpen
,
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
);
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
,
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
);
305 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpRead
, aFileHandle
,
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
,
327 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpRead
, aFileHandle
,
330 // Execute original function
331 return gOriginalNtReadFileScatter(aFileHandle
, aEvent
, aApc
, aApcCtx
,
332 aIoStatus
, aSegments
, aLength
, aOffset
,
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
,
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
);
352 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpWrite
, aFileHandle
,
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
,
375 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpWrite
, aFileHandle
,
378 // Execute original function
379 return gOriginalNtWriteFileGather(aFileHandle
, aEvent
, aApc
, aApcCtx
,
380 aIoStatus
, aSegments
, aLength
, aOffset
,
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
);
394 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpFSync
, aFileHandle
,
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
,
414 aObjectAttributes
? aObjectAttributes
->ObjectName
->Buffer
: L
"";
415 uint32_t len
= aObjectAttributes
416 ? aObjectAttributes
->ObjectName
->Length
/ sizeof(WCHAR
)
418 nsDependentSubstring
filename(buf
, len
);
419 WinIOAutoObservation
timer(mozilla::IOInterposeObserver::OpStat
, filename
);
421 // Execute original function
422 return gOriginalNtQueryFullAttributesFile(aObjectAttributes
,
428 /******************************** IO Poisoning ********************************/
430 // Windows DLL interceptor
431 static mozilla::WindowsDllInterceptor sNtDllInterceptor
;
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")) {
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).
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
);
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");
501 // Destroy the DLL interceptor
503 sNtDllInterceptor
.Clear();
504 sHandleToFilenameCache
->Clear();
508 } // namespace mozilla