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/. */
10 #define SECURITY_WIN32
15 #include <processthreadsapi.h>
18 #pragma comment(lib, "bcrypt.lib")
20 #include "AssemblyPayloads.h"
21 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
22 #include "mozilla/UniquePtr.h"
23 #include "mozilla/WindowsProcessMitigations.h"
24 #include "nsWindowsDllInterceptor.h"
25 #include "nsWindowsHelpers.h"
27 NTSTATUS NTAPI
NtFlushBuffersFile(HANDLE
, PIO_STATUS_BLOCK
);
28 NTSTATUS NTAPI
NtReadFile(HANDLE
, HANDLE
, PIO_APC_ROUTINE
, PVOID
,
29 PIO_STATUS_BLOCK
, PVOID
, ULONG
, PLARGE_INTEGER
,
31 NTSTATUS NTAPI
NtReadFileScatter(HANDLE
, HANDLE
, PIO_APC_ROUTINE
, PVOID
,
32 PIO_STATUS_BLOCK
, PFILE_SEGMENT_ELEMENT
, ULONG
,
33 PLARGE_INTEGER
, PULONG
);
34 NTSTATUS NTAPI
NtWriteFile(HANDLE
, HANDLE
, PIO_APC_ROUTINE
, PVOID
,
35 PIO_STATUS_BLOCK
, PVOID
, ULONG
, PLARGE_INTEGER
,
37 NTSTATUS NTAPI
NtWriteFileGather(HANDLE
, HANDLE
, PIO_APC_ROUTINE
, PVOID
,
38 PIO_STATUS_BLOCK
, PFILE_SEGMENT_ELEMENT
, ULONG
,
39 PLARGE_INTEGER
, PULONG
);
40 NTSTATUS NTAPI
NtQueryFullAttributesFile(POBJECT_ATTRIBUTES
, PVOID
);
41 NTSTATUS NTAPI
LdrLoadDll(PWCHAR filePath
, PULONG flags
,
42 PUNICODE_STRING moduleFileName
, PHANDLE handle
);
43 NTSTATUS NTAPI
LdrUnloadDll(HMODULE
);
45 NTSTATUS NTAPI
NtMapViewOfSection(
46 HANDLE aSection
, HANDLE aProcess
, PVOID
* aBaseAddress
, ULONG_PTR aZeroBits
,
47 SIZE_T aCommitSize
, PLARGE_INTEGER aSectionOffset
, PSIZE_T aViewSize
,
48 SECTION_INHERIT aInheritDisposition
, ULONG aAllocationType
,
49 ULONG aProtectionFlags
);
51 // These pointers are disguised as PVOID to avoid pulling in obscure headers
52 PVOID NTAPI
LdrResolveDelayLoadedAPI(PVOID
, PVOID
, PVOID
, PVOID
, PVOID
, ULONG
);
53 void CALLBACK
ProcessCaretEvents(HWINEVENTHOOK
, DWORD
, HWND
, LONG
, LONG
, DWORD
,
55 void __fastcall
BaseThreadInitThunk(BOOL aIsInitialThread
, void* aStartAddress
,
58 BOOL WINAPI
ApiSetQueryApiSetPresence(PCUNICODE_STRING
, PBOOLEAN
);
60 #if (_WIN32_WINNT < 0x0602)
62 SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy
,
63 PVOID aBuffer
, SIZE_T aBufferLen
);
64 #endif // (_WIN32_WINNT < 0x0602)
66 #define RtlGenRandom SystemFunction036
67 extern "C" BOOLEAN NTAPI
RtlGenRandom(PVOID aRandomBuffer
,
68 ULONG aRandomBufferLength
);
70 using namespace mozilla
;
77 bool operator==(const payload
& other
) const {
78 return (a
== other
.a
&& b
== other
.b
&& c
== other
.c
);
82 extern "C" __declspec(dllexport
) __declspec(noinline
) payload
83 rotatePayload(payload p
) {
91 // payloadNotHooked is a target function for a test to expect a negative result.
92 // We cannot use rotatePayload for that purpose because our detour cannot hook
93 // a function detoured already. Please keep this function always unhooked.
94 extern "C" __declspec(dllexport
) __declspec(noinline
) payload
95 payloadNotHooked(payload p
) {
96 // Do something different from rotatePayload to avoid ICF.
103 // Declared as volatile to prevent optimizers from incorrectly eliding accesses
104 // to it. (See bug 1769001 for a motivating example.)
105 static volatile bool patched_func_called
= false;
107 static WindowsDllInterceptor::FuncHookType
<decltype(&rotatePayload
)>
110 static WindowsDllInterceptor::FuncHookType
<decltype(&payloadNotHooked
)>
111 orig_payloadNotHooked
;
113 static payload
patched_rotatePayload(payload p
) {
114 patched_func_called
= true;
115 return orig_rotatePayload(p
);
118 // Invoke aFunc by taking aArg's contents and using them as aFunc's arguments
119 template <typename OrigFuncT
, typename
... Args
,
120 typename ArgTuple
= std::tuple
<Args
...>, size_t... Indices
>
121 decltype(auto) Apply(OrigFuncT
& aFunc
, ArgTuple
&& aArgs
,
122 std::index_sequence
<Indices
...>) {
123 return std::apply(aFunc
, aArgs
);
126 #define DEFINE_TEST_FUNCTION(calling_convention) \
127 template <typename R, typename... Args, typename... TestArgs> \
128 bool TestFunction(R(calling_convention* aFunc)(Args...), bool (*aPred)(R), \
129 TestArgs&&... aArgs) { \
130 using ArgTuple = std::tuple<Args...>; \
131 using Indices = std::index_sequence_for<Args...>; \
132 ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
133 patched_func_called = false; \
134 return aPred(Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices())) && \
135 patched_func_called; \
138 /* Specialization for functions returning void */ \
139 template <typename PredT, typename... Args, typename... TestArgs> \
140 bool TestFunction(void(calling_convention * aFunc)(Args...), PredT, \
141 TestArgs&&... aArgs) { \
142 using ArgTuple = std::tuple<Args...>; \
143 using Indices = std::index_sequence_for<Args...>; \
144 ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
145 patched_func_called = false; \
146 Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices()); \
147 return patched_func_called; \
150 // C++11 allows empty arguments to macros. clang works just fine. MSVC does the
151 // right thing, but it also throws up warning C4003.
152 #if defined(_MSC_VER) && !defined(__clang__)
153 DEFINE_TEST_FUNCTION(__cdecl
)
155 DEFINE_TEST_FUNCTION()
159 DEFINE_TEST_FUNCTION(__stdcall
)
160 DEFINE_TEST_FUNCTION(__fastcall
)
163 // Test the hooked function against the supplied predicate
164 template <typename OrigFuncT
, typename PredicateT
, typename
... Args
>
165 bool CheckHook(OrigFuncT
& aOrigFunc
, const char* aDllName
,
166 const char* aFuncName
, PredicateT
&& aPred
, Args
&&... aArgs
) {
167 if (TestFunction(aOrigFunc
, std::forward
<PredicateT
>(aPred
),
168 std::forward
<Args
>(aArgs
)...)) {
170 "TEST-PASS | WindowsDllInterceptor | "
171 "Executed hooked function %s from %s\n",
172 aFuncName
, aDllName
);
177 "TEST-FAILED | WindowsDllInterceptor | "
178 "Failed to execute hooked function %s from %s\n",
179 aFuncName
, aDllName
);
183 struct InterceptorFunction
{
184 static const size_t EXEC_MEMBLOCK_SIZE
= 64 * 1024; // 64K
186 static InterceptorFunction
& Create() {
187 // Make sure the executable memory is allocated
193 // Make sure we aren't making more functions than we allocated room for
194 MOZ_RELEASE_ASSERT((sNumInstances
+ 1) * sizeof(InterceptorFunction
) <=
197 // Grab the next InterceptorFunction from executable memory
198 InterceptorFunction
& ret
= *reinterpret_cast<InterceptorFunction
*>(
199 sBlock
+ (sNumInstances
++ * sizeof(InterceptorFunction
)));
201 // Set the InterceptorFunction to the code template.
202 auto funcCode
= &ret
[0];
203 memcpy(funcCode
, sInterceptorTemplate
, TemplateLength
);
205 // Fill in the patched_func_called pointer in the template.
207 reinterpret_cast<volatile bool**>(&ret
[PatchedFuncCalledIndex
]);
208 *pfPtr
= &patched_func_called
;
212 uint8_t& operator[](size_t i
) { return mFuncCode
[i
]; }
214 uint8_t* GetFunction() { return mFuncCode
; }
216 void SetStub(uintptr_t aStub
) {
217 auto pfPtr
= reinterpret_cast<uintptr_t*>(&mFuncCode
[StubFuncIndex
]);
222 // We intercept functions with short machine-code functions that set a boolean
223 // and run the stub that launches the original function. Each entry in the
224 // array is the code for one of those interceptor functions. We cannot
225 // free this memory until the test shuts down.
226 // The templates have spots for the address of patched_func_called
227 // and for the address of the stub function. Their indices in the byte
228 // array are given as constants below and they appear as blocks of
229 // 0xff bytes in the templates.
231 // 0: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &patched_func_called
232 // a: c6 00 01 mov BYTE PTR [rax],0x1
233 // d: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &stub_func_ptr
235 static constexpr uint8_t sInterceptorTemplate
[] = {
236 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
237 0xFF, 0xC6, 0x00, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF,
238 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0};
239 static const size_t PatchedFuncCalledIndex
= 0x2;
240 static const size_t StubFuncIndex
= 0xf;
241 #elif defined(_M_IX86)
242 // 0: c6 05 ff ff ff ff 01 mov BYTE PTR &patched_func_called, 0x1
243 // 7: 68 ff ff ff ff push &stub_func_ptr
245 static constexpr uint8_t sInterceptorTemplate
[] = {
246 0xC6, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
247 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3};
248 static const size_t PatchedFuncCalledIndex
= 0x2;
249 static const size_t StubFuncIndex
= 0x8;
250 #elif defined(_M_ARM64)
251 // 0: 31 00 80 52 movz w17, #0x1
252 // 4: 90 00 00 58 ldr x16, #16
253 // 8: 11 02 00 39 strb w17, [x16]
254 // c: 90 00 00 58 ldr x16, #16
255 // 10: 00 02 1F D6 br x16
256 // 14: &patched_func_called
257 // 1c: &stub_func_ptr
258 static constexpr uint8_t sInterceptorTemplate
[] = {
259 0x31, 0x00, 0x80, 0x52, 0x90, 0x00, 0x00, 0x58, 0x11, 0x02, 0x00, 0x39,
260 0x90, 0x00, 0x00, 0x58, 0x00, 0x02, 0x1F, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF,
261 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
262 static const size_t PatchedFuncCalledIndex
= 0x14;
263 static const size_t StubFuncIndex
= 0x1c;
265 # error "Missing template for architecture"
268 static const size_t TemplateLength
= sizeof(sInterceptorTemplate
);
269 uint8_t mFuncCode
[TemplateLength
];
271 InterceptorFunction() = delete;
272 InterceptorFunction(const InterceptorFunction
&) = delete;
273 InterceptorFunction
& operator=(const InterceptorFunction
&) = delete;
277 sBlock
= reinterpret_cast<uint8_t*>(
278 ::VirtualAlloc(nullptr, EXEC_MEMBLOCK_SIZE
, MEM_RESERVE
| MEM_COMMIT
,
279 PAGE_EXECUTE_READWRITE
));
282 static uint8_t* sBlock
;
283 static size_t sNumInstances
;
286 uint8_t* InterceptorFunction::sBlock
= nullptr;
287 size_t InterceptorFunction::sNumInstances
= 0;
289 constexpr uint8_t InterceptorFunction::sInterceptorTemplate
[];
293 // To check that unwind information propagates from hooked functions to their
294 // stubs, we need to find the real address where the detoured code lives.
295 class RedirectionResolver
: public interceptor::WindowsDllPatcherBase
<
296 interceptor::VMSharingPolicyShared
> {
298 uintptr_t ResolveRedirectedAddressForTest(FARPROC aFunc
) {
299 return ResolveRedirectedAddress(aFunc
).GetAddress();
305 void PrintFunctionBytes(FARPROC aFuncAddr
, uint32_t aNumBytesToDump
) {
306 printf("\tFirst %u bytes of function:\n\t", aNumBytesToDump
);
307 auto code
= reinterpret_cast<const uint8_t*>(aFuncAddr
);
308 for (uint32_t i
= 0; i
< aNumBytesToDump
; ++i
) {
309 char suffix
= (i
< (aNumBytesToDump
- 1)) ? ' ' : '\n';
310 printf("%02hhX%c", code
[i
], suffix
);
316 // Hook the function and optionally attempt calling it
317 template <typename OrigFuncT
, size_t N
, typename PredicateT
, typename
... Args
>
318 bool TestHook(const char (&dll
)[N
], const char* func
, PredicateT
&& aPred
,
321 mozilla::MakeUnique
<WindowsDllInterceptor::FuncHookType
<OrigFuncT
>>());
323 std::copy(std::begin(dll
), std::end(dll
), std::begin(dllW
));
325 HMODULE module
= ::LoadLibraryW(dllW
);
326 FARPROC funcAddr
= ::GetProcAddress(module
, func
);
329 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to find %s from "
338 // Resolve what is the actual address of the code that will be detoured, as
339 // that's the code we want to compare with when we check for unwind
340 // information. Do that *before* detouring, although the address will only be
341 // used after detouring.
342 RedirectionResolver resolver
;
343 auto detouredCodeAddr
= resolver
.ResolveRedirectedAddressForTest(funcAddr
);
347 bool successful
= false;
348 WindowsDllInterceptor TestIntercept
;
349 TestIntercept
.Init(dll
);
351 InterceptorFunction
& interceptorFunc
= InterceptorFunction::Create();
352 successful
= orig_func
->Set(
354 reinterpret_cast<OrigFuncT
>(interceptorFunc
.GetFunction()));
357 auto stub
= reinterpret_cast<uintptr_t>(orig_func
->GetStub());
358 interceptorFunc
.SetStub(stub
);
359 printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func
,
365 // Check that unwind information has been added if and only if it was
366 // present for the original detoured code.
367 uintptr_t funcImageBase
= 0;
369 RtlLookupFunctionEntry(detouredCodeAddr
, &funcImageBase
, nullptr);
370 bool funcHasUnwindInfo
= bool(funcEntry
);
372 uintptr_t stubImageBase
= 0;
373 auto stubEntry
= RtlLookupFunctionEntry(stub
, &stubImageBase
, nullptr);
374 bool stubHasUnwindInfo
= bool(stubEntry
);
376 if (funcHasUnwindInfo
== stubHasUnwindInfo
) {
378 "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s and "
379 "the original function are coherent with respect to unwind info: "
380 "funcHasUnwindInfo (%d) == stubHasUnwindInfo (%d).\n",
381 func
, dll
, funcHasUnwindInfo
, stubHasUnwindInfo
);
385 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook for %s from %s "
386 "and the original function are not coherent with respect to unwind "
388 "funcHasUnwindInfo (%d) != stubHasUnwindInfo (%d).\n",
389 func
, dll
, funcHasUnwindInfo
, stubHasUnwindInfo
);
394 if (stubHasUnwindInfo
) {
395 if (stub
== (stubImageBase
+ stubEntry
->BeginAddress
)) {
397 "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s has "
398 "coherent unwind info.\n",
403 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | The hook for %s "
404 " from %s has incoherent unwind info.\n",
415 "TEST-SKIPPED | WindowsDllInterceptor | "
416 "Will not attempt to execute patched %s.\n",
422 // Test the DLL function we just hooked.
423 return CheckHook(reinterpret_cast<OrigFuncT
&>(funcAddr
), dll
, func
,
424 std::forward
<PredicateT
>(aPred
),
425 std::forward
<Args
>(aArgs
)...);
428 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from "
433 // Print out the function's bytes so that we can easily analyze the error.
434 nsModuleHandle
mod(::LoadLibraryW(dllW
));
435 FARPROC funcAddr
= ::GetProcAddress(mod
, func
);
437 const uint32_t kNumBytesToDump
=
438 WindowsDllInterceptor::GetWorstCaseRequiredBytesToPatch();
439 PrintFunctionBytes(funcAddr
, kNumBytesToDump
);
445 // Detour the function and optionally attempt calling it
446 template <typename OrigFuncT
, size_t N
, typename PredicateT
>
447 bool TestDetour(const char (&dll
)[N
], const char* func
, PredicateT
&& aPred
) {
449 mozilla::MakeUnique
<WindowsDllInterceptor::FuncHookType
<OrigFuncT
>>());
451 std::copy(std::begin(dll
), std::end(dll
), std::begin(dllW
));
453 bool successful
= false;
454 WindowsDllInterceptor TestIntercept
;
455 TestIntercept
.Init(dll
);
457 InterceptorFunction
& interceptorFunc
= InterceptorFunction::Create();
458 successful
= orig_func
->Set(
460 reinterpret_cast<OrigFuncT
>(interceptorFunc
.GetFunction()));
463 interceptorFunc
.SetStub(reinterpret_cast<uintptr_t>(orig_func
->GetStub()));
464 printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n",
469 "TEST-SKIPPED | WindowsDllInterceptor | "
470 "Will not attempt to execute patched %s.\n",
476 // Test the DLL function we just hooked.
477 HMODULE module
= ::LoadLibraryW(dllW
);
478 FARPROC funcAddr
= ::GetProcAddress(module
, func
);
483 return CheckHook(reinterpret_cast<OrigFuncT
&>(funcAddr
), dll
, func
,
484 std::forward
<PredicateT
>(aPred
));
487 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s "
495 // If a function pointer's type returns void*, this template converts that type
496 // to return uintptr_t instead, for the purposes of predicates.
497 template <typename FuncT
>
498 struct SubstituteForVoidPtr
{
502 template <typename
... Args
>
503 struct SubstituteForVoidPtr
<void* (*)(Args
...)> {
504 using Type
= uintptr_t (*)(Args
...);
508 template <typename
... Args
>
509 struct SubstituteForVoidPtr
<void*(__stdcall
*)(Args
...)> {
510 using Type
= uintptr_t(__stdcall
*)(Args
...);
513 template <typename
... Args
>
514 struct SubstituteForVoidPtr
<void*(__fastcall
*)(Args
...)> {
515 using Type
= uintptr_t(__fastcall
*)(Args
...);
519 // Determines the function's return type
520 template <typename FuncT
>
523 template <typename R
, typename
... Args
>
524 struct ReturnType
<R (*)(Args
...)> {
529 template <typename R
, typename
... Args
>
530 struct ReturnType
<R(__stdcall
*)(Args
...)> {
534 template <typename R
, typename
... Args
>
535 struct ReturnType
<R(__fastcall
*)(Args
...)> {
540 // Predicates that may be supplied during tests
541 template <typename FuncT
>
543 using ArgType
= typename ReturnType
<FuncT
>::Type
;
545 template <ArgType CompVal
>
546 static bool Equals(ArgType aValue
) {
547 return CompVal
== aValue
;
550 template <ArgType CompVal
>
551 static bool NotEquals(ArgType aValue
) {
552 return CompVal
!= aValue
;
555 template <ArgType CompVal
>
556 static bool Ignore(ArgType aValue
) {
561 // Functions that return void should be ignored, so we specialize the
562 // Ignore predicate for that case. Use nullptr as the value to compare against.
563 template <typename
... Args
>
564 struct Predicates
<void (*)(Args
...)> {
565 template <nullptr_t DummyVal
>
566 static bool Ignore() {
572 template <typename
... Args
>
573 struct Predicates
<void(__stdcall
*)(Args
...)> {
574 template <nullptr_t DummyVal
>
575 static bool Ignore() {
580 template <typename
... Args
>
581 struct Predicates
<void(__fastcall
*)(Args
...)> {
582 template <nullptr_t DummyVal
>
583 static bool Ignore() {
589 // The standard test. Hook |func|, and then try executing it with all zero
590 // arguments, using |pred| and |comp| to determine whether the call successfully
591 // executed. In general, you want set pred and comp such that they return true
592 // when the function is returning whatever value is expected with all-zero
595 // Note: When |func| returns void, you must supply |Ignore| and |nullptr| as the
596 // |pred| and |comp| arguments, respectively.
597 #define TEST_HOOK_HELPER(dll, func, pred, comp) \
598 TestHook<decltype(&func)>(dll, #func, \
599 &Predicates<decltype(&func)>::pred<comp>)
601 #define TEST_HOOK(dll, func, pred, comp) TEST_HOOK_HELPER(dll, func, pred, comp)
603 // We need to special-case functions that return INVALID_HANDLE_VALUE
604 // (ie, CreateFile). Our template machinery for comparing values doesn't work
605 // with integer constants passed as pointers (well, it works on MSVC, but not
606 // clang, because that is not standard-compliant).
607 #define TEST_HOOK_FOR_INVALID_HANDLE_VALUE(dll, func) \
608 TestHook<SubstituteForVoidPtr<decltype(&func)>::Type>( \
610 &Predicates<SubstituteForVoidPtr<decltype(&func)>::Type>::Equals< \
613 // This variant allows you to explicitly supply arguments to the hooked function
614 // during testing. You want to provide arguments that produce the conditions
615 // that induce the function to return a value that is accepted by your
617 #define TEST_HOOK_PARAMS(dll, func, pred, comp, ...) \
618 TestHook<decltype(&func)>( \
619 dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
621 // This is for cases when we want to hook |func|, but it is unsafe to attempt
622 // to execute the function in the context of a test.
623 #define TEST_HOOK_SKIP_EXEC(dll, func) \
624 TestHook<decltype(&func)>( \
626 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
629 // The following three variants are identical to the previous macros,
630 // however the forcibly use a Detour on 32-bit Windows. On 64-bit Windows,
631 // these macros are identical to their TEST_HOOK variants.
632 #define TEST_DETOUR(dll, func, pred, comp) \
633 TestDetour<decltype(&func)>(dll, #func, \
634 &Predicates<decltype(&func)>::pred<comp>)
636 #define TEST_DETOUR_PARAMS(dll, func, pred, comp, ...) \
637 TestDetour<decltype(&func)>( \
638 dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
640 #define TEST_DETOUR_SKIP_EXEC(dll, func) \
641 TestDetour<decltype(&func)>( \
643 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
646 template <typename OrigFuncT
, size_t N
, typename PredicateT
, typename
... Args
>
647 bool MaybeTestHook(const bool cond
, const char (&dll
)[N
], const char* func
,
648 PredicateT
&& aPred
, Args
&&... aArgs
) {
651 "TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from "
658 return TestHook
<OrigFuncT
>(dll
, func
, std::forward
<PredicateT
>(aPred
),
659 std::forward
<Args
>(aArgs
)...);
662 // Like TEST_HOOK, but the test is only executed when cond is true.
663 #define MAYBE_TEST_HOOK(cond, dll, func, pred, comp) \
664 MaybeTestHook<decltype(&func)>(cond, dll, #func, \
665 &Predicates<decltype(&func)>::pred<comp>)
667 #define MAYBE_TEST_HOOK_PARAMS(cond, dll, func, pred, comp, ...) \
668 MaybeTestHook<decltype(&func)>( \
669 cond, dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
671 #define MAYBE_TEST_HOOK_SKIP_EXEC(cond, dll, func) \
672 MaybeTestHook<decltype(&func)>( \
674 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
677 bool ShouldTestTipTsf() {
678 mozilla::DynamicallyLinkedFunctionPtr
<decltype(&SHGetKnownFolderPath
)>
679 pSHGetKnownFolderPath(L
"shell32.dll", "SHGetKnownFolderPath");
680 if (!pSHGetKnownFolderPath
) {
684 PWSTR commonFilesPath
= nullptr;
685 if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon
, 0, nullptr,
686 &commonFilesPath
))) {
690 wchar_t fullPath
[MAX_PATH
+ 1] = {};
691 wcscpy(fullPath
, commonFilesPath
);
692 wcscat(fullPath
, L
"\\Microsoft Shared\\Ink\\tiptsf.dll");
693 CoTaskMemFree(commonFilesPath
);
695 if (!LoadLibraryW(fullPath
)) {
699 // Leak the module so that it's loaded for the interceptor test
703 static const wchar_t gEmptyUnicodeStringLiteral
[] = L
"";
704 static UNICODE_STRING gEmptyUnicodeString
;
705 static BOOLEAN gIsPresent
;
707 bool HasApiSetQueryApiSetPresence() {
708 mozilla::DynamicallyLinkedFunctionPtr
<decltype(&ApiSetQueryApiSetPresence
)>
709 func(L
"Api-ms-win-core-apiquery-l1-1-0.dll", "ApiSetQueryApiSetPresence");
714 // Prepare gEmptyUnicodeString for the test
715 ::RtlInitUnicodeString(&gEmptyUnicodeString
, gEmptyUnicodeStringLiteral
);
720 // Set this to true to test function unhooking (currently broken).
721 const bool ShouldTestUnhookFunction
= false;
723 #if defined(_M_X64) || defined(_M_ARM64)
725 // Use VMSharingPolicyUnique for the ShortInterceptor, as it needs to
726 // reserve its trampoline memory in a special location.
727 using ShortInterceptor
= mozilla::interceptor::WindowsDllInterceptor
<
728 mozilla::interceptor::VMSharingPolicyUnique
<
729 mozilla::interceptor::MMPolicyInProcess
>>;
731 static ShortInterceptor::FuncHookType
<decltype(&::NtMapViewOfSection
)>
732 orig_NtMapViewOfSection
;
734 #endif // defined(_M_X64) || defined(_M_ARM64)
736 bool TestShortDetour() {
737 #if defined(_M_X64) || defined(_M_ARM64)
738 auto pNtMapViewOfSection
= reinterpret_cast<decltype(&::NtMapViewOfSection
)>(
739 ::GetProcAddress(::GetModuleHandleW(L
"ntdll.dll"), "NtMapViewOfSection"));
740 if (!pNtMapViewOfSection
) {
742 "TEST-FAILED | WindowsDllInterceptor | "
743 "Failed to resolve ntdll!NtMapViewOfSection\n");
748 { // Scope for shortInterceptor
749 ShortInterceptor shortInterceptor
;
750 shortInterceptor
.TestOnlyDetourInit(
752 mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch
);
754 InterceptorFunction
& interceptorFunc
= InterceptorFunction::Create();
755 if (!orig_NtMapViewOfSection
.SetDetour(
756 shortInterceptor
, "NtMapViewOfSection",
757 reinterpret_cast<decltype(&::NtMapViewOfSection
)>(
758 interceptorFunc
.GetFunction()))) {
760 "TEST-FAILED | WindowsDllInterceptor | "
761 "Failed to hook ntdll!NtMapViewOfSection via 10-byte patch\n");
766 interceptorFunc
.SetStub(
767 reinterpret_cast<uintptr_t>(orig_NtMapViewOfSection
.GetStub()));
770 &Predicates
<decltype(&::NtMapViewOfSection
)>::Ignore
<((NTSTATUS
)0)>;
772 if (!CheckHook(pNtMapViewOfSection
, "ntdll.dll", "NtMapViewOfSection",
774 // CheckHook has already printed the error message for us
779 // Now ensure that our hook cleanup worked
780 if (ShouldTestUnhookFunction
) {
782 pNtMapViewOfSection(nullptr, nullptr, nullptr, 0, 0, nullptr, nullptr,
783 ((SECTION_INHERIT
)0), 0, 0);
784 if (NT_SUCCESS(status
)) {
786 "TEST-FAILED | WindowsDllInterceptor | "
787 "Unexpected successful call to ntdll!NtMapViewOfSection after "
788 "removing short-patched hook\n");
794 "TEST-PASS | WindowsDllInterceptor | "
795 "Successfully unhooked ntdll!NtMapViewOfSection via short patch\n");
805 constexpr uintptr_t NoStubAddressCheck
= 0;
806 constexpr uintptr_t ExpectedFail
= 1;
808 const char* mFunctionName
;
809 uintptr_t mExpectedStub
;
812 explicit TestCase(const char* aFunctionName
, uintptr_t aExpectedStub
,
813 bool aSkipExec
= false)
814 : mFunctionName(aFunctionName
),
815 mExpectedStub(aExpectedStub
),
817 mSkipExec(aSkipExec
) {}
818 } g_AssemblyTestCases
[] = {
819 #if defined(__clang__)
820 // We disable these testcases because the code coverage instrumentation injects
821 // code in a way that WindowsDllInterceptor doesn't understand.
822 # ifndef MOZ_CODE_COVERAGE
824 // Since we have PatchIfTargetIsRecognizedTrampoline for x64, we expect the
825 // original jump destination is returned as a stub.
826 TestCase("MovPushRet", JumpDestination
,
827 mozilla::IsUserShadowStackEnabled()),
828 TestCase("MovRaxJump", JumpDestination
),
829 TestCase("DoubleJump", JumpDestination
),
831 // Passing NoStubAddressCheck as the following testcases return
832 // a trampoline address instead of the original destination.
833 TestCase("NearJump", NoStubAddressCheck
),
834 TestCase("OpcodeFF", NoStubAddressCheck
),
835 TestCase("IndirectCall", NoStubAddressCheck
),
836 TestCase("MovImm64", NoStubAddressCheck
),
837 # elif defined(_M_IX86)
838 // Skip the stub address check as we always generate a trampoline for x86.
839 TestCase("PushRet", NoStubAddressCheck
,
840 mozilla::IsUserShadowStackEnabled()),
841 TestCase("MovEaxJump", NoStubAddressCheck
),
842 TestCase("DoubleJump", NoStubAddressCheck
),
843 TestCase("Opcode83", NoStubAddressCheck
),
844 TestCase("LockPrefix", NoStubAddressCheck
),
845 TestCase("LooksLikeLockPrefix", NoStubAddressCheck
),
848 // Skip on Debug build because it hits MOZ_ASSERT_UNREACHABLE.
849 TestCase("UnsupportedOp", ExpectedFail
),
850 # endif // !defined(DEBUG)
851 # endif // MOZ_CODE_COVERAGE
852 #endif // defined(__clang__)
855 template <typename InterceptorType
>
856 bool TestAssemblyFunctions() {
857 static const auto patchedFunction
= []() { patched_func_called
= true; };
859 InterceptorType interceptor
;
860 interceptor
.Init("TestDllInterceptor.exe");
862 for (auto& testCase
: g_AssemblyTestCases
) {
863 if (testCase
.mExpectedStub
== NoStubAddressCheck
&& testCase
.mPatchedOnce
) {
864 // For the testcases with NoStubAddressCheck, we revert a hook by
865 // jumping into the original stub, which is not detourable again.
869 typename
InterceptorType::template FuncHookType
<void (*)()> hook
;
871 hook
.Set(interceptor
, testCase
.mFunctionName
, patchedFunction
);
872 if (testCase
.mExpectedStub
== ExpectedFail
) {
875 "TEST-FAILED | WindowsDllInterceptor | "
876 "Unexpectedly succeeded to detour %s.\n",
877 testCase
.mFunctionName
);
880 #if defined(NIGHTLY_BUILD)
881 const Maybe
<DetourError
>& maybeError
= interceptor
.GetLastDetourError();
882 if (maybeError
.isNothing()) {
884 "TEST-FAILED | WindowsDllInterceptor | "
885 "DetourError was not set on detour error.\n");
888 if (maybeError
.ref().mErrorCode
!=
889 DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR
) {
891 "TEST-FAILED | WindowsDllInterceptor | "
892 "A wrong detour errorcode was set on detour error.\n");
895 #endif // defined(NIGHTLY_BUILD)
896 printf("TEST-PASS | WindowsDllInterceptor | %s\n",
897 testCase
.mFunctionName
);
903 "TEST-FAILED | WindowsDllInterceptor | "
904 "Failed to detour %s.\n",
905 testCase
.mFunctionName
);
909 testCase
.mPatchedOnce
= true;
911 const auto actualStub
= reinterpret_cast<uintptr_t>(hook
.GetStub());
912 if (testCase
.mExpectedStub
!= NoStubAddressCheck
&&
913 actualStub
!= testCase
.mExpectedStub
) {
915 "TEST-FAILED | WindowsDllInterceptor | "
916 "Wrong stub was backed up for %s: %zx\n",
917 testCase
.mFunctionName
, actualStub
);
921 if (testCase
.mSkipExec
) {
923 "TEST-SKIPPED | WindowsDllInterceptor | "
924 "Will not attempt to execute patched %s.\n",
925 testCase
.mFunctionName
);
927 patched_func_called
= false;
929 auto originalFunction
= reinterpret_cast<void (*)()>(
930 GetProcAddress(GetModuleHandleW(nullptr), testCase
.mFunctionName
));
933 if (!patched_func_called
) {
935 "TEST-FAILED | WindowsDllInterceptor | "
936 "Hook from %s was not called\n",
937 testCase
.mFunctionName
);
942 printf("TEST-PASS | WindowsDllInterceptor | %s\n", testCase
.mFunctionName
);
948 #if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
949 // We want to test hooking and unhooking with unwind information, so we need:
950 // - a VMSharingPolicy such that ShouldUnhookUponDestruction() is true and
951 // Items() is implemented;
952 // - a MMPolicy such that ShouldUnhookUponDestruction() is true and
953 // kSupportsUnwindInfo is true.
954 using DetouredCallInterceptor
= mozilla::interceptor::WindowsDllInterceptor
<
955 mozilla::interceptor::VMSharingPolicyUnique
<
956 mozilla::interceptor::MMPolicyInProcess
>>;
958 struct DetouredCallChunk
{
959 alignas(uint32_t) RUNTIME_FUNCTION functionTable
[1];
960 alignas(uint32_t) uint8_t unwindInfo
[sizeof(gDetouredCallUnwindInfo
)];
961 uint8_t code
[gDetouredCallCodeSize
];
964 // Unfortunately using RtlAddFunctionTable for static code that lives within
965 // a module doesn't seem to work. Presumably it conflicts with the static
966 // function tables. So we recreate gDetouredCall as dynamic code to be able to
967 // associate it with unwind information.
968 decltype(&DetouredCallCode
) gDetouredCall
=
969 []() -> decltype(&DetouredCallCode
) {
970 // We first adjust the detoured call jumper from:
971 // ff 25 00 00 00 00 jmp qword ptr [rip + 0]
973 // ff 25 XX XX XX XX jmp qword ptr [rip + offset gDetouredCall]
974 uint8_t bytes
[6]{0xff, 0x25, 0, 0, 0, 0};
975 if (0 != memcmp(bytes
, reinterpret_cast<void*>(DetouredCallJumper
),
981 // Because the current function could be located on the same memory page as
982 // DetouredCallJumper, we must preserve the permission to execute the page
983 // while we adjust the detoured call jumper (hence PAGE_EXECUTE_READWRITE).
984 if (!VirtualProtect(reinterpret_cast<void*>(DetouredCallJumper
), sizeof bytes
,
985 PAGE_EXECUTE_READWRITE
, &oldProtect
)) {
989 *reinterpret_cast<uint32_t*>(&bytes
[2]) = static_cast<uint32_t>(
990 reinterpret_cast<uintptr_t>(&gDetouredCall
) -
991 (reinterpret_cast<uintptr_t>(DetouredCallJumper
) + sizeof bytes
));
992 memcpy(reinterpret_cast<void*>(DetouredCallJumper
), bytes
, sizeof bytes
);
994 if (!VirtualProtect(reinterpret_cast<void*>(DetouredCallJumper
), sizeof bytes
,
995 oldProtect
, &oldProtect
)) {
999 auto detouredCallChunk
= reinterpret_cast<DetouredCallChunk
*>(
1000 VirtualAlloc(nullptr, sizeof(DetouredCallChunk
), MEM_RESERVE
| MEM_COMMIT
,
1002 if (!detouredCallChunk
) {
1006 detouredCallChunk
->functionTable
[0].BeginAddress
=
1007 offsetof(DetouredCallChunk
, code
);
1008 detouredCallChunk
->functionTable
[0].EndAddress
=
1009 offsetof(DetouredCallChunk
, code
) + gDetouredCallCodeSize
;
1010 detouredCallChunk
->functionTable
[0].UnwindData
=
1011 offsetof(DetouredCallChunk
, unwindInfo
);
1012 memcpy(reinterpret_cast<void*>(&detouredCallChunk
->unwindInfo
),
1013 reinterpret_cast<void*>(gDetouredCallUnwindInfo
),
1014 sizeof(detouredCallChunk
->unwindInfo
));
1015 memcpy(reinterpret_cast<void*>(&detouredCallChunk
->code
[0]),
1016 reinterpret_cast<void*>(DetouredCallCode
),
1017 sizeof(detouredCallChunk
->code
));
1019 if (!VirtualProtect(reinterpret_cast<void*>(detouredCallChunk
),
1020 sizeof(DetouredCallChunk
), PAGE_EXECUTE_READ
,
1022 VirtualFree(detouredCallChunk
, 0, MEM_RELEASE
);
1026 if (!RtlAddFunctionTable(detouredCallChunk
->functionTable
, 1,
1027 reinterpret_cast<uintptr_t>(detouredCallChunk
))) {
1028 VirtualFree(detouredCallChunk
, 0, MEM_RELEASE
);
1032 return reinterpret_cast<decltype(&DetouredCallCode
)>(detouredCallChunk
->code
);
1035 // We use our own variable instead of patched_func_called because the callee of
1036 // gDetouredCall could end up calling other, already hooked functions could
1037 // change patched_func_called.
1038 static volatile bool sCalledPatchedDetouredCall
= false;
1039 static volatile bool sCalledDetouredCallCallee
= false;
1040 static volatile bool sCouldUnwindFromDetouredCallCallee
= false;
1041 void DetouredCallCallee() {
1042 sCalledDetouredCallCallee
= true;
1044 // Check that we can fully unwind the stack
1045 CONTEXT contextRecord
{};
1046 RtlCaptureContext(&contextRecord
);
1047 if (!contextRecord
.Rip
) {
1049 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1050 "DetouredCallCallee was unable to get an initial context to work "
1055 while (contextRecord
.Rip
) {
1056 DWORD64 imageBase
= 0;
1057 auto FunctionEntry
=
1058 RtlLookupFunctionEntry(contextRecord
.Rip
, &imageBase
, nullptr);
1059 if (!FunctionEntry
) {
1061 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1062 "DetouredCallCallee was unable to get unwind info for ControlPc=%p\n",
1063 reinterpret_cast<void*>(contextRecord
.Rip
));
1068 "TEST-PASS | WindowsDllInterceptor | "
1069 "DetouredCallCallee was able to get unwind info for ControlPc=%p\n",
1070 reinterpret_cast<void*>(contextRecord
.Rip
));
1072 void* handlerData
= nullptr;
1073 DWORD64 establisherFrame
= 0;
1074 RtlVirtualUnwind(UNW_FLAG_NHANDLER
, imageBase
, contextRecord
.Rip
,
1075 FunctionEntry
, &contextRecord
, &handlerData
,
1076 &establisherFrame
, nullptr);
1078 sCouldUnwindFromDetouredCallCallee
= true;
1081 static DetouredCallInterceptor::FuncHookType
<decltype(&DetouredCallCode
)>
1084 static void patched_DetouredCall(uintptr_t aCallee
) {
1085 sCalledPatchedDetouredCall
= true;
1086 return orig_DetouredCall(aCallee
);
1089 bool TestCallingDetouredCall(const char* aTestDescription
,
1090 bool aExpectCalledPatchedDetouredCall
) {
1091 sCalledPatchedDetouredCall
= false;
1092 sCalledDetouredCallCallee
= false;
1093 sCouldUnwindFromDetouredCallCallee
= false;
1094 DetouredCallJumper(reinterpret_cast<uintptr_t>(DetouredCallCallee
));
1096 if (aExpectCalledPatchedDetouredCall
!= sCalledPatchedDetouredCall
) {
1098 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1099 "%s: expectCalledPatchedDetouredCall (%d) differs from "
1100 "sCalledPatchedDetouredCall (%d)\n",
1101 aTestDescription
, aExpectCalledPatchedDetouredCall
,
1102 sCalledPatchedDetouredCall
);
1108 "TEST-PASS | WindowsDllInterceptor | "
1109 "%s: expectCalledPatchedDetouredCall (%d) matches with "
1110 "sCalledPatchedDetouredCall (%d)\n",
1111 aTestDescription
, aExpectCalledPatchedDetouredCall
,
1112 sCalledPatchedDetouredCall
);
1115 if (!sCalledDetouredCallCallee
) {
1117 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1118 "%s: gDetouredCall failed to call its callee\n",
1125 "TEST-PASS | WindowsDllInterceptor | "
1126 "%s: gDetouredCall successfully called its callee\n",
1130 if (!sCouldUnwindFromDetouredCallCallee
) {
1132 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1133 "%s: the callee of gDetouredCall failed to unwind\n",
1140 "TEST-PASS | WindowsDllInterceptor | "
1141 "%s: the callee of gDetouredCall successfully unwinded\n",
1147 // Test that detouring a call preserves unwind information (bug 1798787).
1148 bool TestDetouredCallUnwindInfo() {
1149 if (!gDetouredCall
) {
1151 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1152 "Failed to generate dynamic gDetouredCall code\n");
1157 uintptr_t imageBase
= 0;
1158 if (!RtlLookupFunctionEntry(reinterpret_cast<uintptr_t>(gDetouredCall
),
1159 &imageBase
, nullptr)) {
1161 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1162 "Failed to find unwind information for dynamic gDetouredCall code\n");
1167 // We first double check that we manage to unwind when we *do not* detour
1168 if (!TestCallingDetouredCall("Before hooking", false)) {
1172 uintptr_t StubAddress
= 0;
1174 // The real test starts here: let's detour and check if we can still unwind
1176 DetouredCallInterceptor ExeIntercept
;
1177 ExeIntercept
.Init("TestDllInterceptor.exe");
1178 if (!orig_DetouredCall
.Set(ExeIntercept
, "DetouredCallJumper",
1179 &patched_DetouredCall
)) {
1181 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1182 "Failed to hook the detoured call jumper.\n");
1188 "TEST-PASS | WindowsDllInterceptor | "
1189 "Successfully hooked the detoured call jumper.\n");
1192 StubAddress
= reinterpret_cast<uintptr_t>(orig_DetouredCall
.GetStub());
1193 if (!RtlLookupFunctionEntry(StubAddress
, &imageBase
, nullptr)) {
1195 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1196 "Failed to find unwind information for detoured code of "
1202 TestCallingDetouredCall("After hooking", true);
1205 // Check that we can still unwind after clearing the hook.
1206 return TestCallingDetouredCall("After unhooking", false);
1208 #endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
1210 #ifndef MOZ_CODE_COVERAGE
1211 # if defined(_M_X64) || defined(_M_IX86)
1212 bool TestSpareBytesAfterDetour() {
1213 WindowsDllInterceptor interceptor
;
1214 interceptor
.Init("TestDllInterceptor.exe");
1215 InterceptorFunction
& interceptorFunc
= InterceptorFunction::Create();
1217 mozilla::MakeUnique
<WindowsDllInterceptor::FuncHookType
<void (*)()>>());
1219 bool successful
= orig_func
->Set(
1220 interceptor
, "SpareBytesAfterDetour",
1221 reinterpret_cast<void (*)()>(interceptorFunc
.GetFunction()));
1224 "TEST-FAILED | WindowsDllInterceptor | "
1225 "Failed to detour SpareBytesAfterDetour.\n");
1229 ::GetProcAddress(GetModuleHandleW(nullptr), "SpareBytesAfterDetour");
1232 "TEST-FAILED | WindowsDllInterceptor | "
1233 "Failed to GetProcAddress() for SpareBytesAfterDetour.\n");
1236 uint8_t* funcBytes
= reinterpret_cast<uint8_t*>(funcAddr
);
1237 # if defined(_M_X64)
1238 // patch is 13 bytes
1239 // the next instruction ends after 17 bytes
1240 if (*(funcBytes
+ 13) != 0x90 || *(funcBytes
+ 14) != 0x90 ||
1241 *(funcBytes
+ 15) != 0x90 || *(funcBytes
+ 16) != 0x90) {
1243 "TEST-FAILED | WindowsDllInterceptor | "
1244 "SpareBytesAfterDetour doesn't have nop's after the patch.\n");
1245 PrintFunctionBytes(funcAddr
, 17);
1249 "TEST-PASS | WindowsDllInterceptor | "
1250 "SpareBytesAfterDetour has correct nop bytes after the patch.\n");
1251 # elif defined(_M_IX86)
1253 // the next instruction ends after 6 bytes
1254 if (*(funcBytes
+ 5) != 0x90) {
1256 "TEST-FAILED | WindowsDllInterceptor | "
1257 "SpareBytesAfterDetour doesn't have nop's after the patch.\n");
1258 PrintFunctionBytes(funcAddr
, 6);
1262 "TEST-PASS | WindowsDllInterceptor | "
1263 "SpareBytesAfterDetour has correct nop bytes after the patch.\n");
1268 # endif // defined(_M_X64) || defined(_M_IX86)
1270 # if defined(_M_X64)
1271 bool TestSpareBytesAfterDetourFor10BytePatch() {
1272 ShortInterceptor interceptor
;
1273 interceptor
.TestOnlyDetourInit(
1274 L
"TestDllInterceptor.exe",
1275 mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch
);
1276 InterceptorFunction
& interceptorFunc
= InterceptorFunction::Create();
1279 mozilla::MakeUnique
<ShortInterceptor::FuncHookType
<void (*)()>>());
1280 bool successful
= orig_func
->SetDetour(
1281 interceptor
, "SpareBytesAfterDetourFor10BytePatch",
1282 reinterpret_cast<void (*)()>(interceptorFunc
.GetFunction()));
1285 "TEST-FAILED | WindowsDllInterceptor | "
1286 "Failed to detour SpareBytesAfterDetourFor10BytePatch.\n");
1289 FARPROC funcAddr
= ::GetProcAddress(GetModuleHandleW(nullptr),
1290 "SpareBytesAfterDetourFor10BytePatch");
1293 "TEST-FAILED | WindowsDllInterceptor | "
1294 "Failed to GetProcAddress() for "
1295 "SpareBytesAfterDetourFor10BytePatch.\n");
1298 uint8_t* funcBytes
= reinterpret_cast<uint8_t*>(funcAddr
);
1299 // patch is 10 bytes
1300 // the next instruction ends after 12 bytes
1301 if (*(funcBytes
+ 10) != 0x90 || *(funcBytes
+ 11) != 0x90) {
1303 "TEST-FAILED | WindowsDllInterceptor | "
1304 "SpareBytesAfterDetourFor10BytePatch doesn't have nop's after the "
1306 PrintFunctionBytes(funcAddr
, 12);
1310 "TEST-PASS | WindowsDllInterceptor | "
1311 "SpareBytesAfterDetourFor10BytePatch has correct nop bytes after the "
1316 #endif // MOZ_CODE_COVERAGE
1318 bool TestDynamicCodePolicy() {
1319 PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy
= {};
1320 policy
.ProhibitDynamicCode
= true;
1322 mozilla::DynamicallyLinkedFunctionPtr
<decltype(&SetProcessMitigationPolicy
)>
1323 pSetProcessMitigationPolicy(L
"kernel32.dll",
1324 "SetProcessMitigationPolicy");
1325 if (!pSetProcessMitigationPolicy
) {
1327 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1328 "SetProcessMitigationPolicy does not exist.\n");
1333 if (!pSetProcessMitigationPolicy(ProcessDynamicCodePolicy
, &policy
,
1336 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1337 "Fail to enable ProcessDynamicCodePolicy.\n");
1342 WindowsDllInterceptor ExeIntercept
;
1343 ExeIntercept
.Init("TestDllInterceptor.exe");
1345 // Make sure we fail to hook a function if ProcessDynamicCodePolicy is on
1346 // because we cannot create an executable trampoline region.
1347 if (orig_payloadNotHooked
.Set(ExeIntercept
, "payloadNotHooked",
1348 &patched_rotatePayload
)) {
1350 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1351 "ProcessDynamicCodePolicy is not working.\n");
1357 "TEST-PASS | WindowsDllInterceptor | "
1358 "Successfully passed TestDynamicCodePolicy.\n");
1363 extern "C" int wmain(int argc
, wchar_t* argv
[]) {
1364 LARGE_INTEGER start
;
1365 QueryPerformanceCounter(&start
);
1367 // We disable this part of the test because the code coverage instrumentation
1368 // injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
1370 #ifndef MOZ_CODE_COVERAGE
1371 payload initial
= {0x12345678, 0xfc4e9d31, 0x87654321};
1373 ZeroMemory(&p0
, sizeof(p0
));
1374 ZeroMemory(&p1
, sizeof(p1
));
1376 p0
= rotatePayload(initial
);
1379 WindowsDllInterceptor ExeIntercept
;
1380 ExeIntercept
.Init("TestDllInterceptor.exe");
1381 if (orig_rotatePayload
.Set(ExeIntercept
, "rotatePayload",
1382 &patched_rotatePayload
)) {
1383 printf("TEST-PASS | WindowsDllInterceptor | Hook added\n");
1387 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add "
1393 p1
= rotatePayload(initial
);
1395 if (patched_func_called
) {
1396 printf("TEST-PASS | WindowsDllInterceptor | Hook called\n");
1400 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not "
1407 printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n");
1411 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return "
1412 "the right information\n");
1418 patched_func_called
= false;
1419 ZeroMemory(&p1
, sizeof(p1
));
1421 p1
= rotatePayload(initial
);
1423 if (ShouldTestUnhookFunction
!= patched_func_called
) {
1425 "TEST-PASS | WindowsDllInterceptor | Hook was %scalled after "
1427 ShouldTestUnhookFunction
? "not " : "");
1431 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was %scalled "
1432 "after unregistration\n",
1433 ShouldTestUnhookFunction
? "" : "not ");
1440 "TEST-PASS | WindowsDllInterceptor | Original function worked "
1445 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function "
1446 "didn't return the right information\n");
1452 CredHandle credHandle
;
1453 memset(&credHandle
, 0, sizeof(CredHandle
));
1454 OBJECT_ATTRIBUTES attributes
= {};
1456 // NB: These tests should be ordered such that lower-level APIs are tested
1457 // before higher-level APIs.
1458 if (TestShortDetour() &&
1459 // Run <ShortInterceptor> first because <WindowsDllInterceptor>
1460 // does not clean up hooks.
1462 TestAssemblyFunctions
<ShortInterceptor
>() &&
1464 TestAssemblyFunctions
<WindowsDllInterceptor
>() &&
1466 // We keep this test to hook complex code on x86. (Bug 850957)
1467 TEST_HOOK("ntdll.dll", NtFlushBuffersFile
, NotEquals
, 0) &&
1469 TEST_HOOK("ntdll.dll", NtCreateFile
, NotEquals
, 0) &&
1470 TEST_HOOK("ntdll.dll", NtReadFile
, NotEquals
, 0) &&
1471 TEST_HOOK("ntdll.dll", NtReadFileScatter
, NotEquals
, 0) &&
1472 TEST_HOOK("ntdll.dll", NtWriteFile
, NotEquals
, 0) &&
1473 TEST_HOOK("ntdll.dll", NtWriteFileGather
, NotEquals
, 0) &&
1474 TEST_HOOK_PARAMS("ntdll.dll", NtQueryFullAttributesFile
, NotEquals
, 0,
1475 &attributes
, nullptr) &&
1476 TEST_DETOUR_SKIP_EXEC("ntdll.dll", LdrLoadDll
) &&
1477 TEST_HOOK("ntdll.dll", LdrUnloadDll
, NotEquals
, 0) &&
1478 TEST_HOOK_SKIP_EXEC("ntdll.dll", LdrResolveDelayLoadedAPI
) &&
1479 MAYBE_TEST_HOOK_PARAMS(HasApiSetQueryApiSetPresence(),
1480 "Api-ms-win-core-apiquery-l1-1-0.dll",
1481 ApiSetQueryApiSetPresence
, Equals
, FALSE
,
1482 &gEmptyUnicodeString
, &gIsPresent
) &&
1483 TEST_HOOK("kernelbase.dll", QueryDosDeviceW
, Equals
, 0) &&
1484 TEST_HOOK("kernel32.dll", GetFileAttributesW
, Equals
,
1485 INVALID_FILE_ATTRIBUTES
) &&
1486 #if !defined(_M_ARM64)
1488 // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp
1489 // This fails on ASan because the ASan runtime already hooked this
1491 TEST_HOOK("kernel32.dll", SetUnhandledExceptionFilter
, Ignore
, nullptr) &&
1493 #endif // !defined(_M_ARM64)
1495 TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileW
) &&
1497 #if !defined(_M_ARM64)
1498 TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileA
) &&
1499 #endif // !defined(_M_ARM64)
1500 #if !defined(_M_ARM64)
1501 TEST_HOOK("kernel32.dll", TlsAlloc
, NotEquals
, TLS_OUT_OF_INDEXES
) &&
1502 TEST_HOOK_PARAMS("kernel32.dll", TlsFree
, Equals
, FALSE
,
1503 TLS_OUT_OF_INDEXES
) &&
1504 TEST_HOOK("kernel32.dll", CloseHandle
, Equals
, FALSE
) &&
1505 TEST_HOOK("kernel32.dll", DuplicateHandle
, Equals
, FALSE
) &&
1506 #endif // !defined(_M_ARM64)
1507 TEST_DETOUR_SKIP_EXEC("kernel32.dll", BaseThreadInitThunk
) &&
1508 #if defined(_M_X64) || defined(_M_ARM64)
1509 TEST_HOOK("user32.dll", GetKeyState
, Ignore
, 0) && // see Bug 1316415
1511 TEST_HOOK("user32.dll", GetWindowInfo
, Equals
, FALSE
) &&
1512 TEST_HOOK("user32.dll", TrackPopupMenu
, Equals
, FALSE
) &&
1513 TEST_DETOUR("user32.dll", CreateWindowExW
, Equals
, nullptr) &&
1514 TEST_HOOK("user32.dll", InSendMessageEx
, Equals
, ISMEX_NOSEND
) &&
1515 TEST_HOOK("user32.dll", SendMessageTimeoutW
, Equals
, 0) &&
1516 TEST_HOOK("user32.dll", SetCursorPos
, NotEquals
, FALSE
) &&
1517 TEST_HOOK("bcrypt.dll", BCryptGenRandom
, Equals
,
1518 static_cast<NTSTATUS
>(STATUS_INVALID_HANDLE
)) &&
1519 TEST_HOOK("advapi32.dll", RtlGenRandom
, Equals
, TRUE
) &&
1520 #if !defined(_M_ARM64)
1521 TEST_HOOK("imm32.dll", ImmGetContext
, Equals
, nullptr) &&
1522 #endif // !defined(_M_ARM64)
1523 TEST_HOOK("imm32.dll", ImmGetCompositionStringW
, Ignore
, 0) &&
1524 TEST_HOOK_SKIP_EXEC("imm32.dll", ImmSetCandidateWindow
) &&
1525 TEST_HOOK("imm32.dll", ImmNotifyIME
, Equals
, 0) &&
1526 TEST_HOOK("comdlg32.dll", GetSaveFileNameW
, Ignore
, FALSE
) &&
1527 TEST_HOOK("comdlg32.dll", GetOpenFileNameW
, Ignore
, FALSE
) &&
1529 TEST_HOOK("comdlg32.dll", PrintDlgW
, Ignore
, 0) &&
1531 MAYBE_TEST_HOOK(ShouldTestTipTsf(), "tiptsf.dll", ProcessCaretEvents
,
1533 TEST_HOOK("wininet.dll", InternetOpenA
, NotEquals
, nullptr) &&
1534 TEST_HOOK("wininet.dll", InternetCloseHandle
, Equals
, FALSE
) &&
1535 TEST_HOOK("wininet.dll", InternetConnectA
, Equals
, nullptr) &&
1536 TEST_HOOK("wininet.dll", InternetQueryDataAvailable
, Equals
, FALSE
) &&
1537 TEST_HOOK("wininet.dll", InternetReadFile
, Equals
, FALSE
) &&
1538 TEST_HOOK("wininet.dll", InternetWriteFile
, Equals
, FALSE
) &&
1539 TEST_HOOK("wininet.dll", InternetSetOptionA
, Equals
, FALSE
) &&
1540 TEST_HOOK("wininet.dll", HttpAddRequestHeadersA
, Equals
, FALSE
) &&
1541 TEST_HOOK("wininet.dll", HttpOpenRequestA
, Equals
, nullptr) &&
1542 TEST_HOOK("wininet.dll", HttpQueryInfoA
, Equals
, FALSE
) &&
1543 TEST_HOOK("wininet.dll", HttpSendRequestA
, Equals
, FALSE
) &&
1544 TEST_HOOK("wininet.dll", HttpSendRequestExA
, Equals
, FALSE
) &&
1545 TEST_HOOK("wininet.dll", HttpEndRequestA
, Equals
, FALSE
) &&
1546 TEST_HOOK("wininet.dll", InternetQueryOptionA
, Equals
, FALSE
) &&
1547 TEST_HOOK("sspicli.dll", AcquireCredentialsHandleA
, NotEquals
,
1549 TEST_HOOK_PARAMS("sspicli.dll", QueryCredentialsAttributesA
, Equals
,
1550 SEC_E_INVALID_HANDLE
, &credHandle
, 0, nullptr) &&
1551 TEST_HOOK_PARAMS("sspicli.dll", FreeCredentialsHandle
, Equals
,
1552 SEC_E_INVALID_HANDLE
, &credHandle
) &&
1553 #if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
1554 TestDetouredCallUnwindInfo() &&
1555 #endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
1556 // We disable these testcases because the code coverage instrumentation injects
1557 // code in a way that WindowsDllInterceptor doesn't understand.
1558 #ifndef MOZ_CODE_COVERAGE
1559 # if defined(_M_X64) || defined(_M_IX86)
1560 TestSpareBytesAfterDetour() &&
1561 # if defined(_M_X64)
1562 TestSpareBytesAfterDetourFor10BytePatch() &&
1563 # endif // defined(_M_X64)
1564 # endif // MOZ_CODE_COVERAGE
1565 #endif // defined(_M_X64) || defined(_M_IX86)
1566 // Run TestDynamicCodePolicy() at the end because the policy is
1568 TestDynamicCodePolicy()) {
1569 printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
1571 LARGE_INTEGER end
, freq
;
1572 QueryPerformanceCounter(&end
);
1574 QueryPerformanceFrequency(&freq
);
1576 LARGE_INTEGER result
;
1577 result
.QuadPart
= end
.QuadPart
- start
.QuadPart
;
1578 result
.QuadPart
*= 1000000;
1579 result
.QuadPart
/= freq
.QuadPart
;
1581 printf("Elapsed time: %lld microseconds\n", result
.QuadPart
);