[SM91] Update to Spidermonkey 91.1.3 APIs
[0ad.git] / libraries / source / spidermonkey / include-win32-release / nsWindowsDllInterceptor.h
blobd6afc9bb7fbd65367a6344c296c2d71f759df66e
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 #ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
8 #define NS_WINDOWS_DLL_INTERCEPTOR_H_
10 #include <wchar.h>
11 #include <windows.h>
12 #include <winternl.h>
14 #include <utility>
16 #include "mozilla/ArrayUtils.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/Atomics.h"
19 #include "mozilla/Attributes.h"
20 #include "mozilla/CheckedInt.h"
21 #include "mozilla/DebugOnly.h"
22 #include "mozilla/NativeNt.h"
23 #include "mozilla/Tuple.h"
24 #include "mozilla/Types.h"
25 #include "mozilla/UniquePtr.h"
26 #include "mozilla/Vector.h"
27 #include "mozilla/interceptor/MMPolicies.h"
28 #include "mozilla/interceptor/PatcherDetour.h"
29 #include "mozilla/interceptor/PatcherNopSpace.h"
30 #include "mozilla/interceptor/VMSharingPolicies.h"
31 #include "nsWindowsHelpers.h"
34 * Simple function interception.
36 * We have two separate mechanisms for intercepting a function: We can use the
37 * built-in nop space, if it exists, or we can create a detour.
39 * Using the built-in nop space works as follows: On x86-32, DLL functions
40 * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
41 * NOP instructions.
43 * When we detect a function with this prelude, we do the following:
45 * 1. Write a long jump to our interceptor function into the five bytes of NOPs
46 * before the function.
48 * 2. Write a short jump -5 into the two-byte nop at the beginning of the
49 * function.
51 * This mechanism is nice because it's thread-safe. It's even safe to do if
52 * another thread is currently running the function we're modifying!
54 * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
55 * but not the long jump, so re-intercepting the same function won't work,
56 * because its prelude won't match.
59 * Unfortunately nop space patching doesn't work on functions which don't have
60 * this magic prelude (and in particular, x86-64 never has the prelude). So
61 * when we can't use the built-in nop space, we fall back to using a detour,
62 * which works as follows:
64 * 1. Save first N bytes of OrigFunction to trampoline, where N is a
65 * number of bytes >= 5 that are instruction aligned.
67 * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
68 * function.
70 * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
71 * continue original program flow.
73 * 4. Hook function needs to call the trampoline during its execution,
74 * to invoke the original function (so address of trampoline is
75 * returned).
77 * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
78 * patched again to jump directly to the trampoline instead of going through
79 * the hook function. As such, re-intercepting the same function won't work, as
80 * jump instructions are not supported.
82 * Note that this is not thread-safe. Sad day.
86 #if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard)
87 // On x86, nop-space patches return to the second instruction of their target.
88 // This is a deliberate violation of Control Flow Guard, so disable the check.
89 # define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf))
90 #else
91 # define INTERCEPTOR_DISABLE_CFGUARD /* nothing */
92 #endif
94 namespace mozilla {
95 namespace interceptor {
97 template <typename T>
98 struct OriginalFunctionPtrTraits;
100 template <typename R, typename... Args>
101 struct OriginalFunctionPtrTraits<R (*)(Args...)> {
102 using ReturnType = R;
105 #if defined(_M_IX86)
106 template <typename R, typename... Args>
107 struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> {
108 using ReturnType = R;
111 template <typename R, typename... Args>
112 struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> {
113 using ReturnType = R;
115 #endif // defined(_M_IX86)
117 template <typename InterceptorT, typename FuncPtrT>
118 class FuncHook final {
119 public:
120 using ThisType = FuncHook<InterceptorT, FuncPtrT>;
121 using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
123 constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {}
125 ~FuncHook() = default;
127 bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
128 LPVOID addHookOk = nullptr;
129 InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false);
131 return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
132 &addHookOk) &&
133 addHookOk;
136 bool SetDetour(InterceptorT& aInterceptor, const char* aName,
137 FuncPtrT aHookDest) {
138 LPVOID addHookOk = nullptr;
139 InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true);
141 return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
142 &addHookOk) &&
143 addHookOk;
146 explicit operator bool() const { return !!mOrigFunc; }
148 template <typename... ArgsType>
149 INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const {
150 return mOrigFunc(std::forward<ArgsType>(aArgs)...);
153 FuncPtrT GetStub() const { return mOrigFunc; }
155 // One-time init stuff cannot be moved or copied
156 FuncHook(const FuncHook&) = delete;
157 FuncHook(FuncHook&&) = delete;
158 FuncHook& operator=(const FuncHook&) = delete;
159 FuncHook& operator=(FuncHook&& aOther) = delete;
161 private:
162 struct MOZ_RAII InitOnceContext final {
163 InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor,
164 const char* aName, FuncPtrT aHookDest, bool aForceDetour)
165 : mHook(aHook),
166 mInterceptor(aInterceptor),
167 mName(aName),
168 mHookDest(reinterpret_cast<void*>(aHookDest)),
169 mForceDetour(aForceDetour) {}
171 ThisType* mHook;
172 InterceptorT* mInterceptor;
173 const char* mName;
174 void* mHookDest;
175 bool mForceDetour;
178 private:
179 bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) {
180 return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
181 reinterpret_cast<void**>(&mOrigFunc));
184 bool ApplyDetour(InterceptorT* aInterceptor, const char* aName,
185 void* aHookDest) {
186 return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
187 reinterpret_cast<void**>(&mOrigFunc));
190 static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
191 PVOID* aOutContext) {
192 MOZ_ASSERT(aOutContext);
194 bool result;
195 auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
196 if (ctx->mForceDetour) {
197 result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName,
198 ctx->mHookDest);
199 } else {
200 result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest);
203 *aOutContext =
204 result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
205 : nullptr;
206 return TRUE;
209 private:
210 FuncPtrT mOrigFunc;
211 INIT_ONCE mInitOnce;
214 template <typename InterceptorT, typename FuncPtrT>
215 class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final {
216 public:
217 using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
218 using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
220 #if defined(DEBUG)
221 FuncHookCrossProcess() {}
222 #endif // defined(DEBUG)
224 bool Set(nt::CrossExecTransferManager& aTransferMgr,
225 InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
226 FuncPtrT origFunc;
227 if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
228 reinterpret_cast<void**>(&origFunc))) {
229 return false;
232 return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
235 bool SetDetour(nt::CrossExecTransferManager& aTransferMgr,
236 InterceptorT& aInterceptor, const char* aName,
237 FuncPtrT aHookDest) {
238 FuncPtrT origFunc;
239 if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
240 reinterpret_cast<void**>(&origFunc))) {
241 return false;
244 return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
247 explicit operator bool() const { return !!mOrigFunc; }
250 * NB: This operator is only meaningful when invoked in the target process!
252 template <typename... ArgsType>
253 ReturnType operator()(ArgsType&&... aArgs) const {
254 return mOrigFunc(std::forward<ArgsType>(aArgs)...);
257 #if defined(DEBUG)
258 FuncHookCrossProcess(const FuncHookCrossProcess&) = delete;
259 FuncHookCrossProcess(FuncHookCrossProcess&&) = delete;
260 FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete;
261 FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete;
262 #endif // defined(DEBUG)
264 private:
265 bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr,
266 InterceptorT& aInterceptor, FuncPtrT aStub) {
267 LauncherVoidResult writeResult =
268 aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT));
269 if (writeResult.isErr()) {
270 #ifdef MOZ_USE_LAUNCHER_ERROR
271 const mozilla::WindowsError& err = writeResult.inspectErr().mError;
272 #else
273 const mozilla::WindowsError& err = writeResult.inspectErr();
274 #endif
275 aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
276 err.AsHResult());
277 return false;
279 return true;
282 private:
283 FuncPtrT mOrigFunc;
286 template <typename MMPolicyT, typename InterceptorT>
287 struct TypeResolver;
289 template <typename InterceptorT>
290 struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> {
291 template <typename FuncPtrT>
292 using FuncHookType = FuncHook<InterceptorT, FuncPtrT>;
295 template <typename InterceptorT>
296 struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> {
297 template <typename FuncPtrT>
298 using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
301 template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared>
302 class WindowsDllInterceptor final
303 : public TypeResolver<typename VMPolicy::MMPolicyT,
304 WindowsDllInterceptor<VMPolicy>> {
305 typedef WindowsDllInterceptor<VMPolicy> ThisType;
307 interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
308 #if defined(_M_IX86)
309 interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT>
310 mNopSpacePatcher;
311 #endif // defined(_M_IX86)
313 HMODULE mModule;
315 public:
316 template <typename... Args>
317 explicit WindowsDllInterceptor(Args&&... aArgs)
318 : mDetourPatcher(std::forward<Args>(aArgs)...)
319 #if defined(_M_IX86)
321 mNopSpacePatcher(std::forward<Args>(aArgs)...)
322 #endif // defined(_M_IX86)
324 mModule(nullptr) {
327 WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
328 WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
329 WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
330 WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
332 ~WindowsDllInterceptor() { Clear(); }
334 template <size_t N>
335 void Init(const char (&aModuleName)[N]) {
336 wchar_t moduleName[N];
338 for (size_t i = 0; i < N; ++i) {
339 MOZ_ASSERT(!(aModuleName[i] & 0x80),
340 "Use wide-character overload for non-ASCII module names");
341 moduleName[i] = aModuleName[i];
344 Init(moduleName);
347 void Init(const wchar_t* aModuleName) {
348 if (mModule) {
349 return;
352 mModule = ::LoadLibraryW(aModuleName);
355 /** Force a specific configuration for testing purposes. NOT to be used in
356 production code! **/
357 void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) {
358 Init(aModuleName);
359 mDetourPatcher.Init(aFlags);
362 void Clear() {
363 if (!mModule) {
364 return;
367 #if defined(_M_IX86)
368 mNopSpacePatcher.Clear();
369 #endif // defined(_M_IX86)
370 mDetourPatcher.Clear();
372 // NB: We intentionally leak mModule
375 #if defined(NIGHTLY_BUILD)
376 const Maybe<DetourError>& GetLastDetourError() const {
377 return mDetourPatcher.GetLastDetourError();
379 #endif // defined(NIGHTLY_BUILD)
380 template <typename... Args>
381 void SetLastDetourError(Args&&... aArgs) {
382 return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...);
385 constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
386 return WindowsDllDetourPatcherPrimitive<
387 typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch();
390 private:
392 * Hook/detour the method aName from the DLL we set in Init so that it calls
393 * aHookDest instead. Returns the original method pointer in aOrigFunc
394 * and returns true if successful.
396 * IMPORTANT: If you use this method, please add your case to the
397 * TestDllInterceptor in order to detect future failures. Even if this
398 * succeeds now, updates to the hooked DLL could cause it to fail in
399 * the future.
401 bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
402 // Use a nop space patch if possible, otherwise fall back to a detour.
403 // This should be the preferred method for adding hooks.
404 if (!mModule) {
405 mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
406 return false;
409 if (!mDetourPatcher.IsPageAccessible(
410 nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
411 mDetourPatcher.SetLastDetourError(
412 DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
413 return false;
416 FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
417 if (!proc) {
418 mDetourPatcher.SetLastDetourError(
419 DetourResultCode::INTERCEPTOR_PROC_NULL);
420 return false;
423 if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
424 mDetourPatcher.SetLastDetourError(
425 DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
426 return false;
429 #if defined(_M_IX86)
430 if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
431 return true;
433 #endif // defined(_M_IX86)
435 return AddDetour(proc, aHookDest, aOrigFunc);
439 * Detour the method aName from the DLL we set in Init so that it calls
440 * aHookDest instead. Returns the original method pointer in aOrigFunc
441 * and returns true if successful.
443 * IMPORTANT: If you use this method, please add your case to the
444 * TestDllInterceptor in order to detect future failures. Even if this
445 * succeeds now, updates to the detoured DLL could cause it to fail in
446 * the future.
448 bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
449 // Generally, code should not call this method directly. Use AddHook unless
450 // there is a specific need to avoid nop space patches.
451 if (!mModule) {
452 mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
453 return false;
456 if (!mDetourPatcher.IsPageAccessible(
457 nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
458 mDetourPatcher.SetLastDetourError(
459 DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
460 return false;
463 FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
464 if (!proc) {
465 mDetourPatcher.SetLastDetourError(
466 DetourResultCode::INTERCEPTOR_PROC_NULL);
467 return false;
470 if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
471 mDetourPatcher.SetLastDetourError(
472 DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
473 return false;
476 return AddDetour(proc, aHookDest, aOrigFunc);
479 bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) {
480 MOZ_ASSERT(mModule && aProc);
482 if (!mDetourPatcher.Initialized()) {
483 DetourFlags flags = DetourFlags::eDefault;
484 #if defined(_M_X64)
485 // NTDLL hooks should attempt to use a 10-byte patch because some
486 // injected DLLs do the same and interfere with our stuff.
487 bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll"));
489 bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater());
490 bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());
492 bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll"));
494 bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) ==
495 reinterpret_cast<void*>(&::DuplicateHandle));
497 // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches.
498 needs10BytePatch |= isWin8Or81 && isKernel32Dll &&
499 (reinterpret_cast<void*>(aProc) ==
500 reinterpret_cast<void*>(&CloseHandle));
502 // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches.
503 needs10BytePatch |= isWin8 && isKernel32Dll &&
504 ((reinterpret_cast<void*>(aProc) ==
505 reinterpret_cast<void*>(&::CreateFileA)) ||
506 isDuplicateHandle);
508 if (needs10BytePatch) {
509 flags |= DetourFlags::eEnable10BytePatch;
512 if (isWin8 && isDuplicateHandle) {
513 // Because we can't detour Win8's KERNELBASE!DuplicateHandle,
514 // we detour kernel32!DuplicateHandle (See bug 1659398).
515 flags |= DetourFlags::eDontResolveRedirection;
517 #endif // defined(_M_X64)
519 mDetourPatcher.Init(flags);
522 return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
525 private:
526 template <typename InterceptorT, typename FuncPtrT>
527 friend class FuncHook;
529 template <typename InterceptorT, typename FuncPtrT>
530 friend class FuncHookCrossProcess;
534 * IAT patching is intended for use when we only want to intercept a function
535 * call originating from a specific module.
537 class WindowsIATPatcher final {
538 public:
539 template <typename FuncPtrT>
540 using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>;
542 private:
543 static bool CheckASCII(const char* aInStr) {
544 while (*aInStr) {
545 if (*aInStr & 0x80) {
546 return false;
548 ++aInStr;
550 return true;
553 static bool AddHook(HMODULE aFromModule, const char* aToModuleName,
554 const char* aTargetFnName, void* aHookDest,
555 Atomic<void*>* aOutOrigFunc) {
556 if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) {
557 return false;
560 // PE Spec requires ASCII names for imported module names
561 const bool isModuleNameAscii = CheckASCII(aToModuleName);
562 MOZ_ASSERT(isModuleNameAscii);
563 if (!isModuleNameAscii) {
564 return false;
567 // PE Spec requires ASCII names for imported function names
568 const bool isTargetFnNameAscii = CheckASCII(aTargetFnName);
569 MOZ_ASSERT(isTargetFnNameAscii);
570 if (!isTargetFnNameAscii) {
571 return false;
574 nt::PEHeaders headers(aFromModule);
575 if (!headers) {
576 return false;
579 PIMAGE_IMPORT_DESCRIPTOR impDesc =
580 headers.GetImportDescriptor(aToModuleName);
581 if (!nt::PEHeaders::IsValid(impDesc)) {
582 // Either aFromModule does not import aToModuleName at load-time, or
583 // aToModuleName is a (currently unsupported) delay-load import.
584 return false;
587 // Resolve the import name table (INT).
588 auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>(
589 impDesc->OriginalFirstThunk);
590 if (!nt::PEHeaders::IsValid(firstINTThunk)) {
591 return false;
594 Maybe<ptrdiff_t> thunkIndex;
596 // Scan the INT for the location of the thunk for the function named
597 // 'aTargetFnName'.
598 for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk;
599 nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) {
600 if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) {
601 // Currently not supporting import by ordinal; this isn't hard to add,
602 // but we won't bother unless necessary.
603 continue;
606 PIMAGE_IMPORT_BY_NAME curThunkFnName =
607 headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>(
608 curINTThunk->u1.AddressOfData);
609 MOZ_ASSERT(curThunkFnName);
610 if (!curThunkFnName) {
611 // Looks like we have a bad name descriptor. Try to continue.
612 continue;
615 // Function name checks MUST be case-sensitive!
616 if (!strcmp(aTargetFnName, curThunkFnName->Name)) {
617 // We found the thunk. Save the index of this thunk, as the IAT thunk
618 // is located at the same index in that table as in the INT.
619 thunkIndex = Some(curINTThunk - firstINTThunk);
620 break;
624 if (thunkIndex.isNothing()) {
625 // We never found a thunk for that function. Perhaps it's not imported?
626 return false;
629 if (thunkIndex.value() < 0) {
630 // That's just wrong.
631 return false;
634 auto firstIATThunk =
635 headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
636 if (!nt::PEHeaders::IsValid(firstIATThunk)) {
637 return false;
640 // Resolve the IAT thunk for the function we want
641 PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()];
642 if (!nt::PEHeaders::IsValid(targetThunk)) {
643 return false;
646 auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function);
648 // Now we can just change out its pointer with our hook function.
649 AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE);
650 if (!prot) {
651 return false;
654 // We do the exchange this way to ensure that *aOutOrigFunc is always valid
655 // once the atomic exchange has taken place.
656 void* tmp;
658 do {
659 tmp = *fnPtr;
660 *aOutOrigFunc = tmp;
661 } while (!fnPtr->compareExchange(tmp, aHookDest));
663 return true;
666 template <typename InterceptorT, typename FuncPtrT>
667 friend class FuncHook;
670 template <typename FuncPtrT>
671 class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS
672 FuncHook<WindowsIATPatcher, FuncPtrT>
673 final {
674 public:
675 using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>;
676 using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
678 constexpr FuncHook()
679 : mInitOnce(INIT_ONCE_STATIC_INIT),
680 mFromModule(nullptr),
681 mOrigFunc(nullptr) {}
683 #if defined(DEBUG)
684 ~FuncHook() = default;
685 #endif // defined(DEBUG)
687 bool Set(const wchar_t* aFromModuleName, const char* aToModuleName,
688 const char* aFnName, FuncPtrT aHookDest) {
689 nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName));
690 if (!fromModule) {
691 return false;
694 return Set(fromModule, aToModuleName, aFnName, aHookDest);
697 // We offer this overload in case the client wants finer-grained control over
698 // loading aFromModule.
699 bool Set(nsModuleHandle& aFromModule, const char* aToModuleName,
700 const char* aFnName, FuncPtrT aHookDest) {
701 LPVOID addHookOk = nullptr;
702 InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest);
704 bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
705 &addHookOk) &&
706 addHookOk;
707 if (!result) {
708 return result;
711 // If we successfully set the hook then we must retain a strong reference
712 // to the module that we modified.
713 mFromModule = aFromModule.disown();
714 return result;
717 explicit operator bool() const { return !!mOrigFunc; }
719 template <typename... ArgsType>
720 ReturnType operator()(ArgsType&&... aArgs) const {
721 return mOrigFunc(std::forward<ArgsType>(aArgs)...);
724 FuncPtrT GetStub() const { return mOrigFunc; }
726 #if defined(DEBUG)
727 // One-time init stuff cannot be moved or copied
728 FuncHook(const FuncHook&) = delete;
729 FuncHook(FuncHook&&) = delete;
730 FuncHook& operator=(const FuncHook&) = delete;
731 FuncHook& operator=(FuncHook&& aOther) = delete;
732 #endif // defined(DEBUG)
734 private:
735 struct MOZ_RAII InitOnceContext final {
736 InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule,
737 const char* aToModuleName, const char* aFnName,
738 FuncPtrT aHookDest)
739 : mHook(aHook),
740 mFromModule(aFromModule),
741 mToModuleName(aToModuleName),
742 mFnName(aFnName),
743 mHookDest(reinterpret_cast<void*>(aHookDest)) {}
745 ThisType* mHook;
746 const nsModuleHandle& mFromModule;
747 const char* mToModuleName;
748 const char* mFnName;
749 void* mHookDest;
752 private:
753 bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName,
754 const char* aFnName, void* aHookDest) {
755 return WindowsIATPatcher::AddHook(
756 aFromModule, aToModuleName, aFnName, aHookDest,
757 reinterpret_cast<Atomic<void*>*>(&mOrigFunc));
760 static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
761 PVOID* aOutContext) {
762 MOZ_ASSERT(aOutContext);
764 auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
765 bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName,
766 ctx->mFnName, ctx->mHookDest);
768 *aOutContext =
769 result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
770 : nullptr;
771 return TRUE;
774 private:
775 INIT_ONCE mInitOnce;
776 HMODULE mFromModule; // never freed
777 FuncPtrT mOrigFunc;
781 * This class applies an irreversible patch to jump to a target function
782 * without backing up the original function.
784 class WindowsDllEntryPointInterceptor final {
785 using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
786 using MMPolicyT = MMPolicyInProcessEarlyStage;
788 MMPolicyT mMMPolicy;
790 public:
791 explicit WindowsDllEntryPointInterceptor(
792 const MMPolicyT::Kernel32Exports& aK32Exports)
793 : mMMPolicy(aK32Exports) {}
795 bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) {
796 if (!aHeaders) {
797 return false;
800 WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher;
801 return patcher.AddIrreversibleHook(
802 mMMPolicy, aHeaders.GetEntryPoint(),
803 reinterpret_cast<uintptr_t>(aDestination));
807 } // namespace interceptor
809 using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
811 using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
812 mozilla::interceptor::VMSharingPolicyUnique<
813 mozilla::interceptor::MMPolicyOutOfProcess>>;
815 using WindowsIATPatcher = interceptor::WindowsIATPatcher;
817 } // namespace mozilla
819 #endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */