com_init_check_hook.cc 13 KB

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