Backed out 15 changesets (bug 1852806) for causing mda failures on test_video_low_pow...
[gecko.git] / toolkit / xre / dllservices / tests / TestDllInterceptor.cpp
blob5dd71e39ca631568d98d289140779ec14ce426c9
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 <shlobj.h>
8 #include <stdio.h>
9 #include <commdlg.h>
10 #define SECURITY_WIN32
11 #include <security.h>
12 #include <wininet.h>
13 #include <schnlsp.h>
14 #include <winternl.h>
15 #include <processthreadsapi.h>
17 #include <bcrypt.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,
30 PULONG);
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,
36 PULONG);
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,
54 DWORD);
55 void __fastcall BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
56 void* aThreadParam);
58 BOOL WINAPI ApiSetQueryApiSetPresence(PCUNICODE_STRING, PBOOLEAN);
60 #if (_WIN32_WINNT < 0x0602)
61 BOOL WINAPI
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;
72 struct payload {
73 UINT64 a;
74 UINT64 b;
75 UINT64 c;
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) {
84 UINT64 tmp = p.a;
85 p.a = p.b;
86 p.b = p.c;
87 p.c = tmp;
88 return 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.
97 p.a ^= p.b;
98 p.b ^= p.c;
99 p.c ^= p.a;
100 return p;
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)>
108 orig_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)
154 #else
155 DEFINE_TEST_FUNCTION()
156 #endif
158 #ifdef _M_IX86
159 DEFINE_TEST_FUNCTION(__stdcall)
160 DEFINE_TEST_FUNCTION(__fastcall)
161 #endif // _M_IX86
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)...)) {
169 printf(
170 "TEST-PASS | WindowsDllInterceptor | "
171 "Executed hooked function %s from %s\n",
172 aFuncName, aDllName);
173 fflush(stdout);
174 return true;
176 printf(
177 "TEST-FAILED | WindowsDllInterceptor | "
178 "Failed to execute hooked function %s from %s\n",
179 aFuncName, aDllName);
180 return false;
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
188 if (!sBlock) {
189 Init();
191 MOZ_ASSERT(sBlock);
193 // Make sure we aren't making more functions than we allocated room for
194 MOZ_RELEASE_ASSERT((sNumInstances + 1) * sizeof(InterceptorFunction) <=
195 EXEC_MEMBLOCK_SIZE);
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.
206 auto pfPtr =
207 reinterpret_cast<volatile bool**>(&ret[PatchedFuncCalledIndex]);
208 *pfPtr = &patched_func_called;
209 return ret;
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]);
218 *pfPtr = aStub;
221 private:
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.
230 #if defined(_M_X64)
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
234 // 17: ff e0 jmp rax
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
244 // c: c3 ret
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;
264 #else
265 # error "Missing template for architecture"
266 #endif
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;
275 static void Init() {
276 MOZ_ASSERT(!sBlock);
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[];
291 #ifdef _M_X64
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> {
297 public:
298 uintptr_t ResolveRedirectedAddressForTest(FARPROC aFunc) {
299 return ResolveRedirectedAddress(aFunc).GetAddress();
303 #endif // _M_X64
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);
313 fflush(stdout);
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,
319 Args&&... aArgs) {
320 auto orig_func(
321 mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
322 wchar_t dllW[N];
323 std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
325 HMODULE module = ::LoadLibraryW(dllW);
326 FARPROC funcAddr = ::GetProcAddress(module, func);
327 if (!funcAddr) {
328 printf(
329 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to find %s from "
330 "%s\n",
331 func, dll);
332 fflush(stdout);
333 return false;
336 #ifdef _M_X64
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);
345 #endif // _M_X64
347 bool successful = false;
348 WindowsDllInterceptor TestIntercept;
349 TestIntercept.Init(dll);
351 InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
352 successful = orig_func->Set(
353 TestIntercept, func,
354 reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
356 if (successful) {
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,
360 dll);
361 fflush(stdout);
363 #ifdef _M_X64
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;
368 auto funcEntry =
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) {
377 printf(
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);
382 fflush(stdout);
383 } else {
384 printf(
385 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook for %s from %s "
386 "and the original function are not coherent with respect to unwind "
387 "info: "
388 "funcHasUnwindInfo (%d) != stubHasUnwindInfo (%d).\n",
389 func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
390 fflush(stdout);
391 return false;
394 if (stubHasUnwindInfo) {
395 if (stub == (stubImageBase + stubEntry->BeginAddress)) {
396 printf(
397 "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s has "
398 "coherent unwind info.\n",
399 func, dll);
400 fflush(stdout);
401 } else {
402 printf(
403 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | The hook for %s "
404 " from %s has incoherent unwind info.\n",
405 func, dll);
406 fflush(stdout);
407 return false;
411 #endif // _M_X64
413 if (!aPred) {
414 printf(
415 "TEST-SKIPPED | WindowsDllInterceptor | "
416 "Will not attempt to execute patched %s.\n",
417 func);
418 fflush(stdout);
419 return true;
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)...);
426 } else {
427 printf(
428 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from "
429 "%s\n",
430 func, dll);
431 fflush(stdout);
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);
436 if (funcAddr) {
437 const uint32_t kNumBytesToDump =
438 WindowsDllInterceptor::GetWorstCaseRequiredBytesToPatch();
439 PrintFunctionBytes(funcAddr, kNumBytesToDump);
441 return false;
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) {
448 auto orig_func(
449 mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
450 wchar_t dllW[N];
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(
459 TestIntercept, func,
460 reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
462 if (successful) {
463 interceptorFunc.SetStub(reinterpret_cast<uintptr_t>(orig_func->GetStub()));
464 printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n",
465 func, dll);
466 fflush(stdout);
467 if (!aPred) {
468 printf(
469 "TEST-SKIPPED | WindowsDllInterceptor | "
470 "Will not attempt to execute patched %s.\n",
471 func);
472 fflush(stdout);
473 return true;
476 // Test the DLL function we just hooked.
477 HMODULE module = ::LoadLibraryW(dllW);
478 FARPROC funcAddr = ::GetProcAddress(module, func);
479 if (!funcAddr) {
480 return false;
483 return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
484 std::forward<PredicateT>(aPred));
485 } else {
486 printf(
487 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s "
488 "from %s\n",
489 func, dll);
490 fflush(stdout);
491 return false;
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 {
499 using Type = FuncT;
502 template <typename... Args>
503 struct SubstituteForVoidPtr<void* (*)(Args...)> {
504 using Type = uintptr_t (*)(Args...);
507 #ifdef _M_IX86
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...);
517 #endif // _M_IX86
519 // Determines the function's return type
520 template <typename FuncT>
521 struct ReturnType;
523 template <typename R, typename... Args>
524 struct ReturnType<R (*)(Args...)> {
525 using Type = R;
528 #ifdef _M_IX86
529 template <typename R, typename... Args>
530 struct ReturnType<R(__stdcall*)(Args...)> {
531 using Type = R;
534 template <typename R, typename... Args>
535 struct ReturnType<R(__fastcall*)(Args...)> {
536 using Type = R;
538 #endif // _M_IX86
540 // Predicates that may be supplied during tests
541 template <typename FuncT>
542 struct Predicates {
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) {
557 return true;
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() {
567 return true;
571 #ifdef _M_IX86
572 template <typename... Args>
573 struct Predicates<void(__stdcall*)(Args...)> {
574 template <nullptr_t DummyVal>
575 static bool Ignore() {
576 return true;
580 template <typename... Args>
581 struct Predicates<void(__fastcall*)(Args...)> {
582 template <nullptr_t DummyVal>
583 static bool Ignore() {
584 return true;
587 #endif // _M_IX86
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
593 // arguments.
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>( \
609 dll, #func, \
610 &Predicates<SubstituteForVoidPtr<decltype(&func)>::Type>::Equals< \
611 uintptr_t(-1)>)
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
616 // predicate.
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)>( \
625 dll, #func, \
626 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
627 NULL))
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)>( \
642 dll, #func, \
643 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
644 NULL))
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) {
649 if (!cond) {
650 printf(
651 "TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from "
652 "%s\n",
653 func, dll);
654 fflush(stdout);
655 return true;
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)>( \
673 cond, dll, #func, \
674 reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
675 NULL))
677 bool ShouldTestTipTsf() {
678 mozilla::DynamicallyLinkedFunctionPtr<decltype(&SHGetKnownFolderPath)>
679 pSHGetKnownFolderPath(L"shell32.dll", "SHGetKnownFolderPath");
680 if (!pSHGetKnownFolderPath) {
681 return false;
684 PWSTR commonFilesPath = nullptr;
685 if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
686 &commonFilesPath))) {
687 return false;
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)) {
696 return false;
699 // Leak the module so that it's loaded for the interceptor test
700 return true;
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");
710 if (!func) {
711 return false;
714 // Prepare gEmptyUnicodeString for the test
715 ::RtlInitUnicodeString(&gEmptyUnicodeString, gEmptyUnicodeStringLiteral);
717 return true;
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) {
741 printf(
742 "TEST-FAILED | WindowsDllInterceptor | "
743 "Failed to resolve ntdll!NtMapViewOfSection\n");
744 fflush(stdout);
745 return false;
748 { // Scope for shortInterceptor
749 ShortInterceptor shortInterceptor;
750 shortInterceptor.TestOnlyDetourInit(
751 L"ntdll.dll",
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()))) {
759 printf(
760 "TEST-FAILED | WindowsDllInterceptor | "
761 "Failed to hook ntdll!NtMapViewOfSection via 10-byte patch\n");
762 fflush(stdout);
763 return false;
766 interceptorFunc.SetStub(
767 reinterpret_cast<uintptr_t>(orig_NtMapViewOfSection.GetStub()));
769 auto pred =
770 &Predicates<decltype(&::NtMapViewOfSection)>::Ignore<((NTSTATUS)0)>;
772 if (!CheckHook(pNtMapViewOfSection, "ntdll.dll", "NtMapViewOfSection",
773 pred)) {
774 // CheckHook has already printed the error message for us
775 return false;
779 // Now ensure that our hook cleanup worked
780 if (ShouldTestUnhookFunction) {
781 NTSTATUS status =
782 pNtMapViewOfSection(nullptr, nullptr, nullptr, 0, 0, nullptr, nullptr,
783 ((SECTION_INHERIT)0), 0, 0);
784 if (NT_SUCCESS(status)) {
785 printf(
786 "TEST-FAILED | WindowsDllInterceptor | "
787 "Unexpected successful call to ntdll!NtMapViewOfSection after "
788 "removing short-patched hook\n");
789 fflush(stdout);
790 return false;
793 printf(
794 "TEST-PASS | WindowsDllInterceptor | "
795 "Successfully unhooked ntdll!NtMapViewOfSection via short patch\n");
796 fflush(stdout);
799 return true;
800 #else
801 return true;
802 #endif
805 constexpr uintptr_t NoStubAddressCheck = 0;
806 constexpr uintptr_t ExpectedFail = 1;
807 struct TestCase {
808 const char* mFunctionName;
809 uintptr_t mExpectedStub;
810 bool mPatchedOnce;
811 bool mSkipExec;
812 explicit TestCase(const char* aFunctionName, uintptr_t aExpectedStub,
813 bool aSkipExec = false)
814 : mFunctionName(aFunctionName),
815 mExpectedStub(aExpectedStub),
816 mPatchedOnce(false),
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
823 # if defined(_M_X64)
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),
846 # endif
847 # if !defined(DEBUG)
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.
866 continue;
869 typename InterceptorType::template FuncHookType<void (*)()> hook;
870 bool result =
871 hook.Set(interceptor, testCase.mFunctionName, patchedFunction);
872 if (testCase.mExpectedStub == ExpectedFail) {
873 if (result) {
874 printf(
875 "TEST-FAILED | WindowsDllInterceptor | "
876 "Unexpectedly succeeded to detour %s.\n",
877 testCase.mFunctionName);
878 return false;
880 #if defined(NIGHTLY_BUILD)
881 const Maybe<DetourError>& maybeError = interceptor.GetLastDetourError();
882 if (maybeError.isNothing()) {
883 printf(
884 "TEST-FAILED | WindowsDllInterceptor | "
885 "DetourError was not set on detour error.\n");
886 return false;
888 if (maybeError.ref().mErrorCode !=
889 DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR) {
890 printf(
891 "TEST-FAILED | WindowsDllInterceptor | "
892 "A wrong detour errorcode was set on detour error.\n");
893 return false;
895 #endif // defined(NIGHTLY_BUILD)
896 printf("TEST-PASS | WindowsDllInterceptor | %s\n",
897 testCase.mFunctionName);
898 continue;
901 if (!result) {
902 printf(
903 "TEST-FAILED | WindowsDllInterceptor | "
904 "Failed to detour %s.\n",
905 testCase.mFunctionName);
906 return false;
909 testCase.mPatchedOnce = true;
911 const auto actualStub = reinterpret_cast<uintptr_t>(hook.GetStub());
912 if (testCase.mExpectedStub != NoStubAddressCheck &&
913 actualStub != testCase.mExpectedStub) {
914 printf(
915 "TEST-FAILED | WindowsDllInterceptor | "
916 "Wrong stub was backed up for %s: %zx\n",
917 testCase.mFunctionName, actualStub);
918 return false;
921 if (testCase.mSkipExec) {
922 printf(
923 "TEST-SKIPPED | WindowsDllInterceptor | "
924 "Will not attempt to execute patched %s.\n",
925 testCase.mFunctionName);
926 } else {
927 patched_func_called = false;
929 auto originalFunction = reinterpret_cast<void (*)()>(
930 GetProcAddress(GetModuleHandleW(nullptr), testCase.mFunctionName));
931 originalFunction();
933 if (!patched_func_called) {
934 printf(
935 "TEST-FAILED | WindowsDllInterceptor | "
936 "Hook from %s was not called\n",
937 testCase.mFunctionName);
938 return false;
942 printf("TEST-PASS | WindowsDllInterceptor | %s\n", testCase.mFunctionName);
945 return true;
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]
972 // to:
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),
976 sizeof bytes)) {
977 return nullptr;
980 DWORD oldProtect{};
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)) {
986 return nullptr;
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)) {
996 return nullptr;
999 auto detouredCallChunk = reinterpret_cast<DetouredCallChunk*>(
1000 VirtualAlloc(nullptr, sizeof(DetouredCallChunk), MEM_RESERVE | MEM_COMMIT,
1001 PAGE_READWRITE));
1002 if (!detouredCallChunk) {
1003 return nullptr;
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,
1021 &oldProtect)) {
1022 VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
1023 return nullptr;
1026 if (!RtlAddFunctionTable(detouredCallChunk->functionTable, 1,
1027 reinterpret_cast<uintptr_t>(detouredCallChunk))) {
1028 VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
1029 return nullptr;
1032 return reinterpret_cast<decltype(&DetouredCallCode)>(detouredCallChunk->code);
1033 }();
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) {
1048 printf(
1049 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1050 "DetouredCallCallee was unable to get an initial context to work "
1051 "with\n");
1052 fflush(stdout);
1053 return;
1055 while (contextRecord.Rip) {
1056 DWORD64 imageBase = 0;
1057 auto FunctionEntry =
1058 RtlLookupFunctionEntry(contextRecord.Rip, &imageBase, nullptr);
1059 if (!FunctionEntry) {
1060 printf(
1061 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1062 "DetouredCallCallee was unable to get unwind info for ControlPc=%p\n",
1063 reinterpret_cast<void*>(contextRecord.Rip));
1064 fflush(stdout);
1065 return;
1067 printf(
1068 "TEST-PASS | WindowsDllInterceptor | "
1069 "DetouredCallCallee was able to get unwind info for ControlPc=%p\n",
1070 reinterpret_cast<void*>(contextRecord.Rip));
1071 fflush(stdout);
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)>
1082 orig_DetouredCall;
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) {
1097 printf(
1098 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1099 "%s: expectCalledPatchedDetouredCall (%d) differs from "
1100 "sCalledPatchedDetouredCall (%d)\n",
1101 aTestDescription, aExpectCalledPatchedDetouredCall,
1102 sCalledPatchedDetouredCall);
1103 fflush(stdout);
1104 return false;
1107 printf(
1108 "TEST-PASS | WindowsDllInterceptor | "
1109 "%s: expectCalledPatchedDetouredCall (%d) matches with "
1110 "sCalledPatchedDetouredCall (%d)\n",
1111 aTestDescription, aExpectCalledPatchedDetouredCall,
1112 sCalledPatchedDetouredCall);
1113 fflush(stdout);
1115 if (!sCalledDetouredCallCallee) {
1116 printf(
1117 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1118 "%s: gDetouredCall failed to call its callee\n",
1119 aTestDescription);
1120 fflush(stdout);
1121 return false;
1124 printf(
1125 "TEST-PASS | WindowsDllInterceptor | "
1126 "%s: gDetouredCall successfully called its callee\n",
1127 aTestDescription);
1128 fflush(stdout);
1130 if (!sCouldUnwindFromDetouredCallCallee) {
1131 printf(
1132 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1133 "%s: the callee of gDetouredCall failed to unwind\n",
1134 aTestDescription);
1135 fflush(stdout);
1136 return false;
1139 printf(
1140 "TEST-PASS | WindowsDllInterceptor | "
1141 "%s: the callee of gDetouredCall successfully unwinded\n",
1142 aTestDescription);
1143 fflush(stdout);
1144 return true;
1147 // Test that detouring a call preserves unwind information (bug 1798787).
1148 bool TestDetouredCallUnwindInfo() {
1149 if (!gDetouredCall) {
1150 printf(
1151 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1152 "Failed to generate dynamic gDetouredCall code\n");
1153 fflush(stdout);
1154 return false;
1157 uintptr_t imageBase = 0;
1158 if (!RtlLookupFunctionEntry(reinterpret_cast<uintptr_t>(gDetouredCall),
1159 &imageBase, nullptr)) {
1160 printf(
1161 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1162 "Failed to find unwind information for dynamic gDetouredCall code\n");
1163 fflush(stdout);
1164 return false;
1167 // We first double check that we manage to unwind when we *do not* detour
1168 if (!TestCallingDetouredCall("Before hooking", false)) {
1169 return 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)) {
1180 printf(
1181 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1182 "Failed to hook the detoured call jumper.\n");
1183 fflush(stdout);
1184 return false;
1187 printf(
1188 "TEST-PASS | WindowsDllInterceptor | "
1189 "Successfully hooked the detoured call jumper.\n");
1190 fflush(stdout);
1192 StubAddress = reinterpret_cast<uintptr_t>(orig_DetouredCall.GetStub());
1193 if (!RtlLookupFunctionEntry(StubAddress, &imageBase, nullptr)) {
1194 printf(
1195 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1196 "Failed to find unwind information for detoured code of "
1197 "gDetouredCall\n");
1198 fflush(stdout);
1199 return false;
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();
1216 auto orig_func(
1217 mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<void (*)()>>());
1219 bool successful = orig_func->Set(
1220 interceptor, "SpareBytesAfterDetour",
1221 reinterpret_cast<void (*)()>(interceptorFunc.GetFunction()));
1222 if (!successful) {
1223 printf(
1224 "TEST-FAILED | WindowsDllInterceptor | "
1225 "Failed to detour SpareBytesAfterDetour.\n");
1226 return false;
1228 FARPROC funcAddr =
1229 ::GetProcAddress(GetModuleHandleW(nullptr), "SpareBytesAfterDetour");
1230 if (!funcAddr) {
1231 printf(
1232 "TEST-FAILED | WindowsDllInterceptor | "
1233 "Failed to GetProcAddress() for SpareBytesAfterDetour.\n");
1234 return false;
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) {
1242 printf(
1243 "TEST-FAILED | WindowsDllInterceptor | "
1244 "SpareBytesAfterDetour doesn't have nop's after the patch.\n");
1245 PrintFunctionBytes(funcAddr, 17);
1246 return false;
1248 printf(
1249 "TEST-PASS | WindowsDllInterceptor | "
1250 "SpareBytesAfterDetour has correct nop bytes after the patch.\n");
1251 # elif defined(_M_IX86)
1252 // patch is 5 bytes
1253 // the next instruction ends after 6 bytes
1254 if (*(funcBytes + 5) != 0x90) {
1255 printf(
1256 "TEST-FAILED | WindowsDllInterceptor | "
1257 "SpareBytesAfterDetour doesn't have nop's after the patch.\n");
1258 PrintFunctionBytes(funcAddr, 6);
1259 return false;
1261 printf(
1262 "TEST-PASS | WindowsDllInterceptor | "
1263 "SpareBytesAfterDetour has correct nop bytes after the patch.\n");
1264 # endif
1266 return true;
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();
1278 auto orig_func(
1279 mozilla::MakeUnique<ShortInterceptor::FuncHookType<void (*)()>>());
1280 bool successful = orig_func->SetDetour(
1281 interceptor, "SpareBytesAfterDetourFor10BytePatch",
1282 reinterpret_cast<void (*)()>(interceptorFunc.GetFunction()));
1283 if (!successful) {
1284 printf(
1285 "TEST-FAILED | WindowsDllInterceptor | "
1286 "Failed to detour SpareBytesAfterDetourFor10BytePatch.\n");
1287 return false;
1289 FARPROC funcAddr = ::GetProcAddress(GetModuleHandleW(nullptr),
1290 "SpareBytesAfterDetourFor10BytePatch");
1291 if (!funcAddr) {
1292 printf(
1293 "TEST-FAILED | WindowsDllInterceptor | "
1294 "Failed to GetProcAddress() for "
1295 "SpareBytesAfterDetourFor10BytePatch.\n");
1296 return false;
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) {
1302 printf(
1303 "TEST-FAILED | WindowsDllInterceptor | "
1304 "SpareBytesAfterDetourFor10BytePatch doesn't have nop's after the "
1305 "patch.\n");
1306 PrintFunctionBytes(funcAddr, 12);
1307 return false;
1309 printf(
1310 "TEST-PASS | WindowsDllInterceptor | "
1311 "SpareBytesAfterDetourFor10BytePatch has correct nop bytes after the "
1312 "patch.\n");
1313 return true;
1315 # endif
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) {
1326 printf(
1327 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1328 "SetProcessMitigationPolicy does not exist.\n");
1329 fflush(stdout);
1330 return false;
1333 if (!pSetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy,
1334 sizeof(policy))) {
1335 printf(
1336 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1337 "Fail to enable ProcessDynamicCodePolicy.\n");
1338 fflush(stdout);
1339 return false;
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)) {
1349 printf(
1350 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
1351 "ProcessDynamicCodePolicy is not working.\n");
1352 fflush(stdout);
1353 return false;
1356 printf(
1357 "TEST-PASS | WindowsDllInterceptor | "
1358 "Successfully passed TestDynamicCodePolicy.\n");
1359 fflush(stdout);
1360 return true;
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
1369 // understand.
1370 #ifndef MOZ_CODE_COVERAGE
1371 payload initial = {0x12345678, 0xfc4e9d31, 0x87654321};
1372 payload p0, p1;
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");
1384 fflush(stdout);
1385 } else {
1386 printf(
1387 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add "
1388 "hook\n");
1389 fflush(stdout);
1390 return 1;
1393 p1 = rotatePayload(initial);
1395 if (patched_func_called) {
1396 printf("TEST-PASS | WindowsDllInterceptor | Hook called\n");
1397 fflush(stdout);
1398 } else {
1399 printf(
1400 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not "
1401 "called\n");
1402 fflush(stdout);
1403 return 1;
1406 if (p0 == p1) {
1407 printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n");
1408 fflush(stdout);
1409 } else {
1410 printf(
1411 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return "
1412 "the right information\n");
1413 fflush(stdout);
1414 return 1;
1418 patched_func_called = false;
1419 ZeroMemory(&p1, sizeof(p1));
1421 p1 = rotatePayload(initial);
1423 if (ShouldTestUnhookFunction != patched_func_called) {
1424 printf(
1425 "TEST-PASS | WindowsDllInterceptor | Hook was %scalled after "
1426 "unregistration\n",
1427 ShouldTestUnhookFunction ? "not " : "");
1428 fflush(stdout);
1429 } else {
1430 printf(
1431 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was %scalled "
1432 "after unregistration\n",
1433 ShouldTestUnhookFunction ? "" : "not ");
1434 fflush(stdout);
1435 return 1;
1438 if (p0 == p1) {
1439 printf(
1440 "TEST-PASS | WindowsDllInterceptor | Original function worked "
1441 "properly\n");
1442 fflush(stdout);
1443 } else {
1444 printf(
1445 "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function "
1446 "didn't return the right information\n");
1447 fflush(stdout);
1448 return 1;
1450 #endif
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.
1461 #if defined(_M_X64)
1462 TestAssemblyFunctions<ShortInterceptor>() &&
1463 #endif
1464 TestAssemblyFunctions<WindowsDllInterceptor>() &&
1465 #ifdef _M_IX86
1466 // We keep this test to hook complex code on x86. (Bug 850957)
1467 TEST_HOOK("ntdll.dll", NtFlushBuffersFile, NotEquals, 0) &&
1468 #endif
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)
1487 # ifndef MOZ_ASAN
1488 // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp
1489 // This fails on ASan because the ASan runtime already hooked this
1490 // function
1491 TEST_HOOK("kernel32.dll", SetUnhandledExceptionFilter, Ignore, nullptr) &&
1492 # endif
1493 #endif // !defined(_M_ARM64)
1494 #ifdef _M_IX86
1495 TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileW) &&
1496 #endif
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
1510 #endif
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) &&
1528 #if defined(_M_X64)
1529 TEST_HOOK("comdlg32.dll", PrintDlgW, Ignore, 0) &&
1530 #endif
1531 MAYBE_TEST_HOOK(ShouldTestTipTsf(), "tiptsf.dll", ProcessCaretEvents,
1532 Ignore, nullptr) &&
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,
1548 SEC_E_OK) &&
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
1567 // irreversible.
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);
1583 return 0;
1586 return 1;