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