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