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