123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- // Copyright 2017 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "base/win/com_init_check_hook.h"
- #include <windows.h>
- #include <objbase.h>
- #include <stdint.h>
- #include <string.h>
- #include <ostream>
- #include <string>
- #include "base/notreached.h"
- #include "base/strings/stringprintf.h"
- #include "base/synchronization/lock.h"
- #include "base/win/com_init_util.h"
- #include "base/win/patch_util.h"
- namespace base {
- namespace win {
- #if defined(COM_INIT_CHECK_HOOK_ENABLED)
- namespace {
- // Hotpatchable Microsoft x86 32-bit functions take one of two forms:
- // Newer format:
- // RelAddr Binary Instruction Remarks
- // -5 cc int 3
- // -4 cc int 3
- // -3 cc int 3
- // -2 cc int 3
- // -1 cc int 3
- // 0 8bff mov edi,edi Actual entry point and no-op.
- // 2 ... Actual body.
- //
- // Older format:
- // RelAddr Binary Instruction Remarks
- // -5 90 nop
- // -4 90 nop
- // -3 90 nop
- // -2 90 nop
- // -1 90 nop
- // 0 8bff mov edi,edi Actual entry point and no-op.
- // 2 ... Actual body.
- //
- // The "int 3" or nop sled as well as entry point no-op are critical, as they
- // are just enough to patch in a short backwards jump to -5 (2 bytes) then that
- // can do a relative 32-bit jump about 2GB before or after the current address.
- //
- // To perform a hotpatch, we need to figure out where we want to go and where
- // we are now as the final jump is relative. Let's say we want to jump to
- // 0x12345678. Relative jumps are calculated from eip, which for our jump is the
- // next instruction address. For the example above, that means we start at a 0
- // base address.
- //
- // Our patch will then look as follows:
- // RelAddr Binary Instruction Remarks
- // -5 e978563412 jmp 0x12345678-(-0x5+0x5) Note little-endian format.
- // 0 ebf9 jmp -0x5-(0x0+0x2) Goes to RelAddr -0x5.
- // 2 ... Actual body.
- // Note: The jmp instructions above are structured as
- // Address(Destination)-(Address(jmp Instruction)+sizeof(jmp Instruction))
- // The struct below is provided for convenience and must be packed together byte
- // by byte with no word alignment padding. This comes at a very small
- // performance cost because now there are shifts handling the fields, but
- // it improves readability.
- #pragma pack(push, 1)
- struct StructuredHotpatch {
- unsigned char jmp_32_relative = 0xe9; // jmp relative 32-bit.
- int32_t relative_address = 0; // 32-bit signed operand.
- unsigned char jmp_8_relative = 0xeb; // jmp relative 8-bit.
- unsigned char back_address = 0xf9; // Operand of -7.
- };
- #pragma pack(pop)
- static_assert(sizeof(StructuredHotpatch) == 7,
- "Needs to be exactly 7 bytes for the hotpatch to work.");
- // nop Function Padding with "mov edi,edi"
- const unsigned char g_hotpatch_placeholder_nop[] = {0x90, 0x90, 0x90, 0x90,
- 0x90, 0x8b, 0xff};
- // int 3 Function Padding with "mov edi,edi"
- const unsigned char g_hotpatch_placeholder_int3[] = {0xcc, 0xcc, 0xcc, 0xcc,
- 0xcc, 0x8b, 0xff};
- // http://crbug.com/1312659: Unusable apphelp placeholder missing one byte.
- const unsigned char g_hotpatch_placeholder_apphelp[] = {0x00, 0xcc, 0xcc, 0xcc,
- 0xcc, 0x8b, 0xff};
- class HookManager {
- public:
- static HookManager* GetInstance() {
- static auto* hook_manager = new HookManager();
- return hook_manager;
- }
- HookManager(const HookManager&) = delete;
- HookManager& operator=(const HookManager&) = delete;
- void RegisterHook() {
- AutoLock auto_lock(lock_);
- ++init_count_;
- if (disabled_)
- return;
- if (init_count_ == 1)
- WriteHook();
- }
- void UnregisterHook() {
- AutoLock auto_lock(lock_);
- DCHECK_NE(0U, init_count_);
- --init_count_;
- if (disabled_)
- return;
- if (init_count_ == 0)
- RevertHook();
- }
- void DisableCOMChecksForProcess() {
- AutoLock auto_lock(lock_);
- if (disabled_)
- return;
- disabled_ = true;
- if (init_count_ > 0)
- RevertHook();
- }
- private:
- enum class HotpatchPlaceholderFormat {
- // The hotpatch placeholder is currently unknown
- UNKNOWN,
- // The hotpatch placeholder used int 3's in the sled.
- INT3,
- // The hotpatch placeholder used nop's in the sled.
- NOP,
- // The hotpatch placeholder is an unusable apphelp shim.
- APPHELP_SHIM,
- // This function has already been patched by a different component.
- EXTERNALLY_PATCHED,
- };
- HookManager() = default;
- ~HookManager() = default;
- void WriteHook() {
- lock_.AssertAcquired();
- DCHECK(!ole32_library_);
- ole32_library_ = ::LoadLibrary(L"ole32.dll");
- if (!ole32_library_)
- return;
- // See banner comment above why this subtracts 5 bytes.
- co_create_instance_padded_address_ =
- reinterpret_cast<uint32_t>(
- GetProcAddress(ole32_library_, "CoCreateInstance")) -
- 5;
- // See banner comment above why this adds 7 bytes.
- original_co_create_instance_body_function_ =
- reinterpret_cast<decltype(original_co_create_instance_body_function_)>(
- co_create_instance_padded_address_ + 7);
- uint32_t dchecked_co_create_instance_address =
- reinterpret_cast<uint32_t>(&HookManager::DCheckedCoCreateInstance);
- uint32_t jmp_offset_base_address = co_create_instance_padded_address_ + 5;
- structured_hotpatch_.relative_address = static_cast<int32_t>(
- dchecked_co_create_instance_address - jmp_offset_base_address);
- HotpatchPlaceholderFormat format = GetHotpatchPlaceholderFormat(
- reinterpret_cast<const void*>(co_create_instance_padded_address_));
- if (format == HotpatchPlaceholderFormat::UNKNOWN) {
- NOTREACHED() << "Unrecognized hotpatch function format: "
- << FirstSevenBytesToString(
- co_create_instance_padded_address_);
- return;
- } else if (format == HotpatchPlaceholderFormat::EXTERNALLY_PATCHED) {
- hotpatch_placeholder_format_ = format;
- NOTREACHED() << "CoCreateInstance appears to be previously patched. <"
- << FirstSevenBytesToString(
- co_create_instance_padded_address_)
- << "> Attempted to write <"
- << FirstSevenBytesToString(
- reinterpret_cast<uint32_t>(&structured_hotpatch_))
- << ">";
- return;
- } else if (format == HotpatchPlaceholderFormat::APPHELP_SHIM) {
- // The apphelp shim placeholder does not allocate enough bytes for a
- // trampolined jump. In this case, we skip patching.
- hotpatch_placeholder_format_ = format;
- return;
- }
- DCHECK_EQ(hotpatch_placeholder_format_, HotpatchPlaceholderFormat::UNKNOWN);
- DWORD patch_result = internal::ModifyCode(
- reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<void*>(&structured_hotpatch_),
- sizeof(structured_hotpatch_));
- if (patch_result == NO_ERROR)
- hotpatch_placeholder_format_ = format;
- }
- void RevertHook() {
- lock_.AssertAcquired();
- DWORD revert_result = NO_ERROR;
- switch (hotpatch_placeholder_format_) {
- case HotpatchPlaceholderFormat::INT3:
- if (WasHotpatchChanged())
- return;
- revert_result = internal::ModifyCode(
- reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&g_hotpatch_placeholder_int3),
- sizeof(g_hotpatch_placeholder_int3));
- break;
- case HotpatchPlaceholderFormat::NOP:
- if (WasHotpatchChanged())
- return;
- revert_result = internal::ModifyCode(
- reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&g_hotpatch_placeholder_nop),
- sizeof(g_hotpatch_placeholder_nop));
- break;
- case HotpatchPlaceholderFormat::EXTERNALLY_PATCHED:
- case HotpatchPlaceholderFormat::APPHELP_SHIM:
- case HotpatchPlaceholderFormat::UNKNOWN:
- break;
- }
- DCHECK_EQ(revert_result, static_cast<DWORD>(NO_ERROR))
- << "Failed to revert CoCreateInstance hot-patch";
- hotpatch_placeholder_format_ = HotpatchPlaceholderFormat::UNKNOWN;
- if (ole32_library_) {
- ::FreeLibrary(ole32_library_);
- ole32_library_ = nullptr;
- }
- co_create_instance_padded_address_ = 0;
- original_co_create_instance_body_function_ = nullptr;
- }
- HotpatchPlaceholderFormat GetHotpatchPlaceholderFormat(const void* address) {
- if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&g_hotpatch_placeholder_int3),
- sizeof(g_hotpatch_placeholder_int3)) == 0) {
- return HotpatchPlaceholderFormat::INT3;
- }
- if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&g_hotpatch_placeholder_nop),
- sizeof(g_hotpatch_placeholder_nop)) == 0) {
- return HotpatchPlaceholderFormat::NOP;
- }
- if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&g_hotpatch_placeholder_apphelp),
- sizeof(g_hotpatch_placeholder_apphelp)) == 0) {
- return HotpatchPlaceholderFormat::APPHELP_SHIM;
- }
- const unsigned char* instruction_bytes =
- reinterpret_cast<const unsigned char*>(
- co_create_instance_padded_address_);
- const unsigned char entry_point_byte = instruction_bytes[5];
- // Check for all of the common jmp opcodes.
- if (entry_point_byte == 0xeb || entry_point_byte == 0xe9 ||
- entry_point_byte == 0xff || entry_point_byte == 0xea) {
- return HotpatchPlaceholderFormat::EXTERNALLY_PATCHED;
- }
- return HotpatchPlaceholderFormat::UNKNOWN;
- }
- bool WasHotpatchChanged() {
- if (::memcmp(reinterpret_cast<void*>(co_create_instance_padded_address_),
- reinterpret_cast<const void*>(&structured_hotpatch_),
- sizeof(structured_hotpatch_)) == 0) {
- return false;
- }
- NOTREACHED() << "CoCreateInstance patch overwritten. Expected: <"
- << FirstSevenBytesToString(co_create_instance_padded_address_)
- << ">, Actual: <"
- << FirstSevenBytesToString(
- reinterpret_cast<uint32_t>(&structured_hotpatch_))
- << ">";
- return true;
- }
- // Indirect call to original_co_create_instance_body_function_ triggers CFI
- // so this function must have CFI disabled.
- static DISABLE_CFI_ICALL HRESULT __stdcall DCheckedCoCreateInstance(
- const CLSID& rclsid,
- IUnknown* pUnkOuter,
- DWORD dwClsContext,
- REFIID riid,
- void** ppv) {
- // Chromium COM callers need to make sure that their thread is configured to
- // process COM objects to avoid creating an implicit MTA or silently failing
- // STA object creation call due to the SUCCEEDED() pattern for COM calls.
- //
- // If you hit this assert as part of migrating to the Task Scheduler,
- // evaluate your threading guarantees and dispatch your work with
- // base::ThreadPool::CreateCOMSTATaskRunner().
- //
- // If you need MTA support, ping //base/task/thread_pool/OWNERS.
- AssertComInitialized(
- "CoCreateInstance calls in Chromium require explicit COM "
- "initialization via base::ThreadPool::CreateCOMSTATaskRunner() or "
- "ScopedCOMInitializer. See the comment in DCheckedCoCreateInstance for "
- "more details.");
- return original_co_create_instance_body_function_(rclsid, pUnkOuter,
- dwClsContext, riid, ppv);
- }
- // Returns the first 7 bytes in hex as a string at |address|.
- static std::string FirstSevenBytesToString(uint32_t address) {
- const unsigned char* bytes =
- reinterpret_cast<const unsigned char*>(address);
- return base::StringPrintf("%02x %02x %02x %02x %02x %02x %02x", bytes[0],
- bytes[1], bytes[2], bytes[3], bytes[4], bytes[5],
- bytes[6]);
- }
- // Synchronizes everything in this class.
- base::Lock lock_;
- size_t init_count_ = 0;
- bool disabled_ = false;
- HMODULE ole32_library_ = nullptr;
- uint32_t co_create_instance_padded_address_ = 0;
- HotpatchPlaceholderFormat hotpatch_placeholder_format_ =
- HotpatchPlaceholderFormat::UNKNOWN;
- StructuredHotpatch structured_hotpatch_;
- static decltype(
- ::CoCreateInstance)* original_co_create_instance_body_function_;
- };
- decltype(::CoCreateInstance)*
- HookManager::original_co_create_instance_body_function_ = nullptr;
- } // namespace
- #endif // defined(COM_INIT_CHECK_HOOK_ENABLED)
- ComInitCheckHook::ComInitCheckHook() {
- #if defined(COM_INIT_CHECK_HOOK_ENABLED)
- HookManager::GetInstance()->RegisterHook();
- #endif // defined(COM_INIT_CHECK_HOOK_ENABLED)
- }
- ComInitCheckHook::~ComInitCheckHook() {
- #if defined(COM_INIT_CHECK_HOOK_ENABLED)
- HookManager::GetInstance()->UnregisterHook();
- #endif // defined(COM_INIT_CHECK_HOOK_ENABLED)
- }
- void ComInitCheckHook::DisableCOMChecksForProcess() {
- #if defined(COM_INIT_CHECK_HOOK_ENABLED)
- HookManager::GetInstance()->DisableCOMChecksForProcess();
- #endif
- }
- } // namespace win
- } // namespace base
|