• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9 
10 #include "base/win/com_init_check_hook.h"
11 
12 #include <objbase.h>
13 
14 #include <windows.h>
15 
16 #include <stdint.h>
17 #include <string.h>
18 
19 #include <ostream>
20 #include <string>
21 
22 #include "base/notreached.h"
23 #include "base/strings/stringprintf.h"
24 #include "base/synchronization/lock.h"
25 #include "base/win/com_init_util.h"
26 #include "base/win/patch_util.h"
27 
28 namespace base {
29 namespace win {
30 
31 #if defined(COM_INIT_CHECK_HOOK_ENABLED)
32 
33 namespace {
34 
35 // Hotpatchable Microsoft x86 32-bit functions take one of two forms:
36 // Newer format:
37 // RelAddr  Binary     Instruction                 Remarks
38 //      -5  cc         int 3
39 //      -4  cc         int 3
40 //      -3  cc         int 3
41 //      -2  cc         int 3
42 //      -1  cc         int 3
43 //       0  8bff       mov edi,edi                 Actual entry point and no-op.
44 //       2  ...                                    Actual body.
45 //
46 // Older format:
47 // RelAddr  Binary     Instruction                 Remarks
48 //      -5  90         nop
49 //      -4  90         nop
50 //      -3  90         nop
51 //      -2  90         nop
52 //      -1  90         nop
53 //       0  8bff       mov edi,edi                 Actual entry point and no-op.
54 //       2  ...                                    Actual body.
55 //
56 // The "int 3" or nop sled as well as entry point no-op are critical, as they
57 // are just enough to patch in a short backwards jump to -5 (2 bytes) then that
58 // can do a relative 32-bit jump about 2GB before or after the current address.
59 //
60 // To perform a hotpatch, we need to figure out where we want to go and where
61 // we are now as the final jump is relative. Let's say we want to jump to
62 // 0x12345678. Relative jumps are calculated from eip, which for our jump is the
63 // next instruction address. For the example above, that means we start at a 0
64 // base address.
65 //
66 // Our patch will then look as follows:
67 // RelAddr  Binary     Instruction                 Remarks
68 //      -5  e978563412 jmp 0x12345678-(-0x5+0x5)   Note little-endian format.
69 //       0  ebf9       jmp -0x5-(0x0+0x2)          Goes to RelAddr -0x5.
70 //       2  ...                                    Actual body.
71 // Note: The jmp instructions above are structured as
72 //       Address(Destination)-(Address(jmp Instruction)+sizeof(jmp Instruction))
73 
74 // The struct below is provided for convenience and must be packed together byte
75 // by byte with no word alignment padding. This comes at a very small
76 // performance cost because now there are shifts handling the fields, but
77 // it improves readability.
78 #pragma pack(push, 1)
79 struct StructuredHotpatch {
80   unsigned char jmp_32_relative = 0xe9;  // jmp relative 32-bit.
81   int32_t relative_address = 0;          // 32-bit signed operand.
82   unsigned char jmp_8_relative = 0xeb;   // jmp relative 8-bit.
83   unsigned char back_address = 0xf9;     // Operand of -7.
84 };
85 #pragma pack(pop)
86 
87 static_assert(sizeof(StructuredHotpatch) == 7,
88               "Needs to be exactly 7 bytes for the hotpatch to work.");
89 
90 // nop Function Padding with "mov edi,edi"
91 const unsigned char g_hotpatch_placeholder_nop[] = {0x90, 0x90, 0x90, 0x90,
92                                                     0x90, 0x8b, 0xff};
93 
94 // int 3 Function Padding with "mov edi,edi"
95 const unsigned char g_hotpatch_placeholder_int3[] = {0xcc, 0xcc, 0xcc, 0xcc,
96                                                      0xcc, 0x8b, 0xff};
97 
98 // http://crbug.com/1312659: Unusable apphelp placeholder missing one byte.
99 const unsigned char g_hotpatch_placeholder_apphelp[] = {0x00, 0xcc, 0xcc, 0xcc,
100                                                         0xcc, 0x8b, 0xff};
101 
102 class HookManager {
103  public:
GetInstance()104   static HookManager* GetInstance() {
105     static auto* hook_manager = new HookManager();
106     return hook_manager;
107   }
108 
109   HookManager(const HookManager&) = delete;
110   HookManager& operator=(const HookManager&) = delete;
111 
RegisterHook()112   void RegisterHook() {
113     AutoLock auto_lock(lock_);
114     ++init_count_;
115     if (disabled_)
116       return;
117     if (init_count_ == 1)
118       WriteHook();
119   }
120 
UnregisterHook()121   void UnregisterHook() {
122     AutoLock auto_lock(lock_);
123     DCHECK_NE(0U, init_count_);
124     --init_count_;
125     if (disabled_)
126       return;
127     if (init_count_ == 0)
128       RevertHook();
129   }
130 
DisableCOMChecksForProcess()131   void DisableCOMChecksForProcess() {
132     AutoLock auto_lock(lock_);
133     if (disabled_)
134       return;
135     disabled_ = true;
136     if (init_count_ > 0)
137       RevertHook();
138   }
139 
140  private:
141   enum class HotpatchPlaceholderFormat {
142     // The hotpatch placeholder is currently unknown
143     UNKNOWN,
144     // The hotpatch placeholder used int 3's in the sled.
145     INT3,
146     // The hotpatch placeholder used nop's in the sled.
147     NOP,
148     // The hotpatch placeholder is an unusable apphelp shim.
149     APPHELP_SHIM,
150     // This function has already been patched by a different component.
151     EXTERNALLY_PATCHED,
152   };
153 
154   HookManager() = default;
155   ~HookManager() = default;
156 
WriteHook()157   void WriteHook() {
158     lock_.AssertAcquired();
159     DCHECK(!ole32_library_);
160     ole32_library_ = ::LoadLibrary(L"ole32.dll");
161 
162     if (!ole32_library_)
163       return;
164 
165     // See banner comment above why this subtracts 5 bytes.
166     co_create_instance_padded_address_ =
167         reinterpret_cast<uint32_t>(
168             GetProcAddress(ole32_library_, "CoCreateInstance")) -
169         5;
170 
171     // See banner comment above why this adds 7 bytes.
172     original_co_create_instance_body_function_ =
173         reinterpret_cast<decltype(original_co_create_instance_body_function_)>(
174             co_create_instance_padded_address_ + 7);
175 
176     uint32_t dchecked_co_create_instance_address =
177         reinterpret_cast<uint32_t>(&HookManager::DCheckedCoCreateInstance);
178     uint32_t jmp_offset_base_address = co_create_instance_padded_address_ + 5;
179     structured_hotpatch_.relative_address = static_cast<int32_t>(
180         dchecked_co_create_instance_address - jmp_offset_base_address);
181 
182     HotpatchPlaceholderFormat format = GetHotpatchPlaceholderFormat(
183         reinterpret_cast<const void*>(co_create_instance_padded_address_));
184     if (format == HotpatchPlaceholderFormat::UNKNOWN) {
185       NOTREACHED() << "Unrecognized hotpatch function format: "
186                    << FirstSevenBytesToString(
187                           co_create_instance_padded_address_);
188     } else if (format == HotpatchPlaceholderFormat::EXTERNALLY_PATCHED) {
189       NOTREACHED() << "CoCreateInstance appears to be previously patched. <"
190                    << FirstSevenBytesToString(
191                           co_create_instance_padded_address_)
192                    << "> Attempted to write <"
193                    << FirstSevenBytesToString(
194                           reinterpret_cast<uint32_t>(&structured_hotpatch_))
195                    << ">";
196     } else if (format == HotpatchPlaceholderFormat::APPHELP_SHIM) {
197       // The apphelp shim placeholder does not allocate enough bytes for a
198       // trampolined jump. In this case, we skip patching.
199       hotpatch_placeholder_format_ = format;
200       return;
201     }
202 
203     DCHECK_EQ(hotpatch_placeholder_format_, HotpatchPlaceholderFormat::UNKNOWN);
204     DWORD patch_result = internal::ModifyCode(
205         reinterpret_cast<void*>(co_create_instance_padded_address_),
206         reinterpret_cast<void*>(&structured_hotpatch_),
207         sizeof(structured_hotpatch_));
208     if (patch_result == NO_ERROR)
209       hotpatch_placeholder_format_ = format;
210   }
211 
RevertHook()212   void RevertHook() {
213     lock_.AssertAcquired();
214 
215     DWORD revert_result = NO_ERROR;
216     switch (hotpatch_placeholder_format_) {
217       case HotpatchPlaceholderFormat::INT3:
218         if (WasHotpatchChanged())
219           return;
220         revert_result = internal::ModifyCode(
221             reinterpret_cast<void*>(co_create_instance_padded_address_),
222             reinterpret_cast<const void*>(&g_hotpatch_placeholder_int3),
223             sizeof(g_hotpatch_placeholder_int3));
224         break;
225       case HotpatchPlaceholderFormat::NOP:
226         if (WasHotpatchChanged())
227           return;
228         revert_result = internal::ModifyCode(
229             reinterpret_cast<void*>(co_create_instance_padded_address_),
230             reinterpret_cast<const void*>(&g_hotpatch_placeholder_nop),
231             sizeof(g_hotpatch_placeholder_nop));
232         break;
233       case HotpatchPlaceholderFormat::EXTERNALLY_PATCHED:
234       case HotpatchPlaceholderFormat::APPHELP_SHIM:
235       case HotpatchPlaceholderFormat::UNKNOWN:
236         break;
237     }
238     DCHECK_EQ(revert_result, static_cast<DWORD>(NO_ERROR))
239         << "Failed to revert CoCreateInstance hot-patch";
240 
241     hotpatch_placeholder_format_ = HotpatchPlaceholderFormat::UNKNOWN;
242 
243     if (ole32_library_) {
244       ::FreeLibrary(ole32_library_);
245       ole32_library_ = nullptr;
246     }
247 
248     co_create_instance_padded_address_ = 0;
249     original_co_create_instance_body_function_ = nullptr;
250   }
251 
GetHotpatchPlaceholderFormat(const void * address)252   HotpatchPlaceholderFormat GetHotpatchPlaceholderFormat(const void* address) {
253     if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
254                  reinterpret_cast<const void*>(&g_hotpatch_placeholder_int3),
255                  sizeof(g_hotpatch_placeholder_int3)) == 0) {
256       return HotpatchPlaceholderFormat::INT3;
257     }
258 
259     if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
260                  reinterpret_cast<const void*>(&g_hotpatch_placeholder_nop),
261                  sizeof(g_hotpatch_placeholder_nop)) == 0) {
262       return HotpatchPlaceholderFormat::NOP;
263     }
264 
265     if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
266                  reinterpret_cast<const void*>(&g_hotpatch_placeholder_apphelp),
267                  sizeof(g_hotpatch_placeholder_apphelp)) == 0) {
268       return HotpatchPlaceholderFormat::APPHELP_SHIM;
269     }
270 
271     const unsigned char* instruction_bytes =
272         reinterpret_cast<const unsigned char*>(
273             co_create_instance_padded_address_);
274     const unsigned char entry_point_byte = instruction_bytes[5];
275     // Check for all of the common jmp opcodes.
276     if (entry_point_byte == 0xeb || entry_point_byte == 0xe9 ||
277         entry_point_byte == 0xff || entry_point_byte == 0xea) {
278       return HotpatchPlaceholderFormat::EXTERNALLY_PATCHED;
279     }
280 
281     return HotpatchPlaceholderFormat::UNKNOWN;
282   }
283 
WasHotpatchChanged()284   bool WasHotpatchChanged() {
285     if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
286                  reinterpret_cast<const void*>(&structured_hotpatch_),
287                  sizeof(structured_hotpatch_)) == 0) {
288       return false;
289     }
290 
291     NOTREACHED() << "CoCreateInstance patch overwritten. Expected: <"
292                  << FirstSevenBytesToString(co_create_instance_padded_address_)
293                  << ">, Actual: <"
294                  << FirstSevenBytesToString(
295                         reinterpret_cast<uint32_t>(&structured_hotpatch_))
296                  << ">";
297   }
298 
299   // Indirect call to original_co_create_instance_body_function_ triggers CFI
300   // so this function must have CFI disabled.
DCheckedCoCreateInstance(const CLSID & rclsid,IUnknown * pUnkOuter,DWORD dwClsContext,REFIID riid,void ** ppv)301   DISABLE_CFI_ICALL static HRESULT __stdcall DCheckedCoCreateInstance(
302       const CLSID& rclsid,
303       IUnknown* pUnkOuter,
304       DWORD dwClsContext,
305       REFIID riid,
306       void** ppv) {
307     // Chromium COM callers need to make sure that their thread is configured to
308     // process COM objects to avoid creating an implicit MTA or silently failing
309     // STA object creation call due to the SUCCEEDED() pattern for COM calls.
310     //
311     // If you hit this assert as part of migrating to the Task Scheduler,
312     // evaluate your threading guarantees and dispatch your work with
313     // base::ThreadPool::CreateCOMSTATaskRunner().
314     //
315     // If you need MTA support, ping //base/task/thread_pool/OWNERS.
316     AssertComInitialized(
317         "CoCreateInstance calls in Chromium require explicit COM "
318         "initialization via base::ThreadPool::CreateCOMSTATaskRunner() or "
319         "ScopedCOMInitializer. See the comment in DCheckedCoCreateInstance for "
320         "more details.");
321     return original_co_create_instance_body_function_(rclsid, pUnkOuter,
322                                                       dwClsContext, riid, ppv);
323   }
324 
325   // Returns the first 7 bytes in hex as a string at |address|.
FirstSevenBytesToString(uint32_t address)326   static std::string FirstSevenBytesToString(uint32_t address) {
327     const unsigned char* bytes =
328         reinterpret_cast<const unsigned char*>(address);
329     return base::StringPrintf("%02x %02x %02x %02x %02x %02x %02x", bytes[0],
330                               bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
331                               bytes[6]);
332   }
333 
334   // Synchronizes everything in this class.
335   base::Lock lock_;
336   size_t init_count_ = 0;
337   bool disabled_ = false;
338   HMODULE ole32_library_ = nullptr;
339   uint32_t co_create_instance_padded_address_ = 0;
340   HotpatchPlaceholderFormat hotpatch_placeholder_format_ =
341       HotpatchPlaceholderFormat::UNKNOWN;
342   StructuredHotpatch structured_hotpatch_;
343   static decltype(
344       ::CoCreateInstance)* original_co_create_instance_body_function_;
345 };
346 
347 decltype(::CoCreateInstance)*
348     HookManager::original_co_create_instance_body_function_ = nullptr;
349 
350 }  // namespace
351 
352 #endif  // defined(COM_INIT_CHECK_HOOK_ENABLED)
353 
ComInitCheckHook()354 ComInitCheckHook::ComInitCheckHook() {
355 #if defined(COM_INIT_CHECK_HOOK_ENABLED)
356   HookManager::GetInstance()->RegisterHook();
357 #endif  // defined(COM_INIT_CHECK_HOOK_ENABLED)
358 }
359 
~ComInitCheckHook()360 ComInitCheckHook::~ComInitCheckHook() {
361 #if defined(COM_INIT_CHECK_HOOK_ENABLED)
362   HookManager::GetInstance()->UnregisterHook();
363 #endif  // defined(COM_INIT_CHECK_HOOK_ENABLED)
364 }
365 
DisableCOMChecksForProcess()366 void ComInitCheckHook::DisableCOMChecksForProcess() {
367 #if defined(COM_INIT_CHECK_HOOK_ENABLED)
368   HookManager::GetInstance()->DisableCOMChecksForProcess();
369 #endif
370 }
371 
372 }  // namespace win
373 }  // namespace base
374