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