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 https://mozilla.org/MPL/2.0/. */
6 
7 #ifndef mozilla_interceptor_PatcherNopSpace_h
8 #define mozilla_interceptor_PatcherNopSpace_h
9 
10 #if defined(_M_IX86)
11 
12 #  include "mozilla/interceptor/PatcherBase.h"
13 
14 namespace mozilla {
15 namespace interceptor {
16 
17 template <typename VMPolicy>
18 class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy> {
19   typedef typename VMPolicy::MMPolicyT MMPolicyT;
20 
21   // For remembering the addresses of functions we've patched.
22   mozilla::Vector<void*> mPatchedFns;
23 
24  public:
25   template <typename... Args>
WindowsDllNopSpacePatcher(Args &&...aArgs)26   explicit WindowsDllNopSpacePatcher(Args&&... aArgs)
27       : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
28 
~WindowsDllNopSpacePatcher()29   ~WindowsDllNopSpacePatcher() { Clear(); }
30 
31   WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
32   WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
33   WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) =
34       delete;
35   WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
36 
Clear()37   void Clear() {
38     // Restore the mov edi, edi to the beginning of each function we patched.
39 
40     for (auto&& ptr : mPatchedFns) {
41       WritableTargetFunction<MMPolicyT> fn(
42           this->mVMPolicy, reinterpret_cast<uintptr_t>(ptr), sizeof(uint16_t));
43       if (!fn) {
44         continue;
45       }
46 
47       // mov edi, edi
48       fn.CommitAndWriteShort(0xff8b);
49     }
50 
51     mPatchedFns.clear();
52   }
53 
54   /**
55    * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
56    * in our address space. There is a bug in Detours 2.x that causes it to
57    * patch at the wrong address when attempting to detour code that is already
58    * NOP space patched. This function is an effort to detect the presence of
59    * this NVIDIA code in our address space and disable NOP space patching if it
60    * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
61    * drivers use to inject into our process.
62    */
IsCompatible()63   static bool IsCompatible() {
64     // These DLLs are known to have bad interactions with this style of patching
65     const wchar_t* kIncompatibleDLLs[] = {L"detoured.dll", L"_etoured.dll",
66                                           L"nvd3d9wrap.dll", L"nvdxgiwrap.dll"};
67     // See if the infringing DLLs are already loaded
68     for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
69       if (GetModuleHandleW(kIncompatibleDLLs[i])) {
70         return false;
71       }
72     }
73     if (GetModuleHandleW(L"user32.dll")) {
74       // user32 is loaded but the infringing DLLs are not, assume we're safe to
75       // proceed.
76       return true;
77     }
78     // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
79     // won't be loaded once user32 is initialized.
80     HKEY hkey = NULL;
81     if (!RegOpenKeyExW(
82             HKEY_LOCAL_MACHINE,
83             L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0,
84             KEY_QUERY_VALUE, &hkey)) {
85       nsAutoRegKey key(hkey);
86       DWORD numBytes = 0;
87       const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
88       // Query for required buffer size
89       LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
90                                      nullptr, &numBytes);
91       mozilla::UniquePtr<wchar_t[]> data;
92       if (!status) {
93         // Allocate the buffer and query for the actual data
94         data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
95         status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
96                                   (LPBYTE)data.get(), &numBytes);
97       }
98       if (!status) {
99         // For each token, split up the filename components and then check the
100         // name of the file.
101         const wchar_t kDelimiters[] = L", ";
102         wchar_t* tokenContext = nullptr;
103         wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
104         while (token) {
105           wchar_t fname[_MAX_FNAME] = {0};
106           if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0, fname,
107                              mozilla::ArrayLength(fname), nullptr, 0)) {
108             // nvinit.dll is responsible for bootstrapping the DLL injection, so
109             // that is the library that we check for here
110             const wchar_t kNvInitName[] = L"nvinit";
111             if (!_wcsnicmp(fname, kNvInitName,
112                            mozilla::ArrayLength(kNvInitName))) {
113               return false;
114             }
115           }
116           token = wcstok_s(nullptr, kDelimiters, &tokenContext);
117         }
118       }
119     }
120     return true;
121   }
122 
AddHook(FARPROC aTargetFn,intptr_t aHookDest,void ** aOrigFunc)123   bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
124     if (!IsCompatible()) {
125 #  if defined(MOZILLA_INTERNAL_API)
126       NS_WARNING("NOP space patching is unavailable for compatibility reasons");
127 #  endif
128       return false;
129     }
130 
131     MOZ_ASSERT(aTargetFn);
132     if (!aTargetFn) {
133       return false;
134     }
135 
136     ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
137         this->ResolveRedirectedAddress(aTargetFn));
138 
139     if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
140       return false;
141     }
142 
143     return mPatchedFns.append(
144         reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
145   }
146 
WriteHook(const ReadOnlyTargetFunction<MMPolicyT> & aFn,intptr_t aHookDest,void ** aOrigFunc)147   bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
148                  intptr_t aHookDest, void** aOrigFunc) {
149     // Ensure we can read and write starting at fn - 5 (for the long jmp we're
150     // going to write) and ending at fn + 2 (for the short jmp up to the long
151     // jmp). These bytes may span two pages with different protection.
152     WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
153     if (!writableFn) {
154       return false;
155     }
156 
157     // Check that the 5 bytes before the function are NOP's or INT 3's,
158     const uint8_t nopOrBp[] = {0x90, 0xCC};
159     if (!writableFn.template VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
160       return false;
161     }
162 
163     // ... and that the first 2 bytes of the function are mov(edi, edi).
164     // There are two ways to encode the same thing:
165     //
166     //   0x89 0xff == mov r/m, r
167     //   0x8b 0xff == mov r, r/m
168     //
169     // where "r" is register and "r/m" is register or memory.
170     // Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
171 
172     // (These look backwards because little-endian)
173     const uint16_t possibleEncodings[] = {0xFF8B, 0xFF89};
174     if (!writableFn.template VerifyValuesAreOneOf<uint16_t, 1>(
175             possibleEncodings, 5)) {
176       return false;
177     }
178 
179     // Write a long jump into the space above the function.
180     writableFn.WriteByte(0xe9);  // jmp
181     if (!writableFn) {
182       return false;
183     }
184 
185     writableFn.WriteDisp32(aHookDest);  // target
186     if (!writableFn) {
187       return false;
188     }
189 
190     // Set aOrigFunc here, because after this point, aHookDest might be called,
191     // and aHookDest might use the aOrigFunc pointer.
192     *aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
193                                          sizeof(uint16_t));
194 
195     // Short jump up into our long jump.
196     return writableFn.CommitAndWriteShort(0xF9EB);  // jmp $-5
197   }
198 };
199 
200 }  // namespace interceptor
201 }  // namespace mozilla
202 
203 #endif  // defined(_M_IX86)
204 
205 #endif  // mozilla_interceptor_PatcherNopSpace_h
206