// 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 <objbase.h>
#include <shlobj.h>
#include <wrl/client.h>

#include "base/test/gtest_util.h"
#include "base/win/com_init_util.h"
#include "base/win/patch_util.h"
#include "base/win/scoped_com_initializer.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace base {
namespace win {

using Microsoft::WRL::ComPtr;

TEST(ComInitCheckHook, AssertNotInitialized) {
  ComInitCheckHook com_check_hook;
  AssertComApartmentType(ComApartmentType::NONE);
  ComPtr<IUnknown> shell_link;
#if defined(COM_INIT_CHECK_HOOK_ENABLED)
  EXPECT_DCHECK_DEATH(::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                                         IID_PPV_ARGS(&shell_link)));
#else
  EXPECT_EQ(CO_E_NOTINITIALIZED,
            ::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                               IID_PPV_ARGS(&shell_link)));
#endif
}

TEST(ComInitCheckHook, HookRemoval) {
  AssertComApartmentType(ComApartmentType::NONE);
  { ComInitCheckHook com_check_hook; }
  ComPtr<IUnknown> shell_link;
  EXPECT_EQ(CO_E_NOTINITIALIZED,
            ::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                               IID_PPV_ARGS(&shell_link)));
}

TEST(ComInitCheckHook, NoAssertComInitialized) {
  ComInitCheckHook com_check_hook;
  ScopedCOMInitializer com_initializer;
  ComPtr<IUnknown> shell_link;
  EXPECT_TRUE(SUCCEEDED(::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                                           IID_PPV_ARGS(&shell_link))));
}

TEST(ComInitCheckHook, MultipleHooks) {
  ComInitCheckHook com_check_hook_1;
  ComInitCheckHook com_check_hook_2;
  AssertComApartmentType(ComApartmentType::NONE);
  ComPtr<IUnknown> shell_link;
#if defined(COM_INIT_CHECK_HOOK_ENABLED)
  EXPECT_DCHECK_DEATH(::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                                         IID_PPV_ARGS(&shell_link)));
#else
  EXPECT_EQ(CO_E_NOTINITIALIZED,
            ::CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_ALL,
                               IID_PPV_ARGS(&shell_link)));
#endif
}

TEST(ComInitCheckHook, UnexpectedHook) {
#if defined(COM_INIT_CHECK_HOOK_ENABLED)
  HMODULE ole32_library = ::LoadLibrary(L"ole32.dll");
  ASSERT_TRUE(ole32_library);

  uint32_t co_create_instance_padded_address =
      reinterpret_cast<uint32_t>(
          GetProcAddress(ole32_library, "CoCreateInstance")) - 5;
  const unsigned char* co_create_instance_bytes =
      reinterpret_cast<const unsigned char*>(co_create_instance_padded_address);
  const unsigned char original_byte = co_create_instance_bytes[0];
  const unsigned char unexpected_byte = 0xdb;
  ASSERT_EQ(static_cast<DWORD>(NO_ERROR),
            internal::ModifyCode(
                reinterpret_cast<void*>(co_create_instance_padded_address),
                reinterpret_cast<const void*>(&unexpected_byte),
                sizeof(unexpected_byte)));

  EXPECT_DCHECK_DEATH({ ComInitCheckHook com_check_hook; });

  // If this call fails, really bad things are going to happen to other tests
  // so CHECK here.
  CHECK_EQ(static_cast<DWORD>(NO_ERROR),
           internal::ModifyCode(
               reinterpret_cast<void*>(co_create_instance_padded_address),
               reinterpret_cast<const void*>(&original_byte),
               sizeof(original_byte)));

  ::FreeLibrary(ole32_library);
  ole32_library = nullptr;
#endif
}

TEST(ComInitCheckHook, ExternallyHooked) {
#if defined(COM_INIT_CHECK_HOOK_ENABLED)
  HMODULE ole32_library = ::LoadLibrary(L"ole32.dll");
  ASSERT_TRUE(ole32_library);

  uint32_t co_create_instance_address = reinterpret_cast<uint32_t>(
      GetProcAddress(ole32_library, "CoCreateInstance"));
  const unsigned char* co_create_instance_bytes =
      reinterpret_cast<const unsigned char*>(co_create_instance_address);
  const unsigned char original_byte = co_create_instance_bytes[0];
  const unsigned char jmp_byte = 0xe9;
  ASSERT_EQ(static_cast<DWORD>(NO_ERROR),
            internal::ModifyCode(
                reinterpret_cast<void*>(co_create_instance_address),
                reinterpret_cast<const void*>(&jmp_byte), sizeof(jmp_byte)));

  // Externally patched instances should crash so we catch these cases on bots.
  EXPECT_DCHECK_DEATH({ ComInitCheckHook com_check_hook; });

  // If this call fails, really bad things are going to happen to other tests
  // so CHECK here.
  CHECK_EQ(
      static_cast<DWORD>(NO_ERROR),
      internal::ModifyCode(reinterpret_cast<void*>(co_create_instance_address),
                           reinterpret_cast<const void*>(&original_byte),
                           sizeof(original_byte)));

  ::FreeLibrary(ole32_library);
  ole32_library = nullptr;
#endif
}

}  // namespace win
}  // namespace base
