| // Copyright 2010 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/win/win_util.h" |
| |
| #include <objbase.h> |
| |
| #include <winternl.h> |
| |
| #include <ntstatus.h> |
| |
| #include <string_view> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/files/file_path.h" |
| #include "base/process/process.h" |
| #include "base/process/process_handle.h" |
| #include "base/scoped_environment_variable_override.h" |
| #include "base/scoped_native_library.h" |
| #include "base/strings/cstring_view.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_co_mem.h" |
| #include "base/win/scoped_com_initializer.h" |
| #include "base/win/windows_handle_util.h" |
| #include "base/win/windows_version.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace base { |
| namespace win { |
| |
| namespace { |
| |
| // Saves the current thread's locale ID when initialized, and restores it when |
| // the instance is going out of scope. |
| class ThreadLocaleSaver { |
| public: |
| ThreadLocaleSaver() : original_locale_id_(GetThreadLocale()) {} |
| |
| ThreadLocaleSaver(const ThreadLocaleSaver&) = delete; |
| ThreadLocaleSaver& operator=(const ThreadLocaleSaver&) = delete; |
| |
| ~ThreadLocaleSaver() { SetThreadLocale(original_locale_id_); } |
| |
| private: |
| LCID original_locale_id_; |
| }; |
| |
| auto* csm_false = static_cast<bool (*)()>([]() -> bool { return false; }); |
| |
| auto* csm_true = static_cast<bool (*)()>([]() -> bool { return true; }); |
| |
| void TestUnicodeStringToView(wcstring_view test) { |
| UNICODE_STRING teststr = {}; |
| ::RtlInitUnicodeString(&teststr, test.c_str()); |
| std::wstring_view view = UnicodeStringToView(teststr); |
| EXPECT_EQ(view, test); |
| // Pointer comparison. |
| EXPECT_EQ(view.data(), test.data()); |
| EXPECT_EQ(std::size(view), std::size(test)); |
| } |
| |
| void TestViewToUnicodeString(std::wstring_view view) { |
| UNICODE_STRING str; |
| EXPECT_TRUE(ViewToUnicodeString(view, str)); |
| // Pointer comparison. |
| EXPECT_EQ(str.Buffer, view.data()); |
| EXPECT_EQ(str.Length, view.size() * sizeof(WCHAR)); |
| EXPECT_EQ(str.Length, str.MaximumLength); |
| } |
| |
| } // namespace |
| |
| // The test is somewhat silly, because some bots some have UAC enabled and some |
| // have it disabled. At least we check that it does not crash. |
| TEST(BaseWinUtilTest, TestIsUACEnabled) { |
| UserAccountControlIsEnabled(); |
| } |
| |
| TEST(BaseWinUtilTest, TestGetUserSidString) { |
| std::wstring user_sid; |
| EXPECT_TRUE(GetUserSidString(&user_sid)); |
| EXPECT_TRUE(!user_sid.empty()); |
| } |
| |
| TEST(BaseWinUtilTest, TestGetLoadedModulesSnapshot) { |
| std::vector<HMODULE> snapshot; |
| |
| ASSERT_TRUE(GetLoadedModulesSnapshot(::GetCurrentProcess(), &snapshot)); |
| size_t original_snapshot_size = snapshot.size(); |
| ASSERT_GT(original_snapshot_size, 0u); |
| snapshot.clear(); |
| |
| // Load in a new module. Pick zipfldr.dll as it is present from WinXP to |
| // Win10, including ARM64 Win10, and yet rarely used. |
| const FilePath::CharType dll_name[] = FILE_PATH_LITERAL("zipfldr.dll"); |
| ASSERT_EQ(nullptr, ::GetModuleHandle(dll_name)); |
| |
| ScopedNativeLibrary new_dll((FilePath(dll_name))); |
| ASSERT_NE(static_cast<HMODULE>(nullptr), new_dll.get()); |
| ASSERT_TRUE(GetLoadedModulesSnapshot(::GetCurrentProcess(), &snapshot)); |
| ASSERT_GT(snapshot.size(), original_snapshot_size); |
| ASSERT_TRUE(Contains(snapshot, new_dll.get())); |
| } |
| |
| TEST(BaseWinUtilTest, TestUint32ToInvalidHandle) { |
| // Ensure that INVALID_HANDLE_VALUE is preserved when going to a 32-bit value |
| // and back on 64-bit platforms. |
| uint32_t invalid_handle = HandleToUint32(INVALID_HANDLE_VALUE); |
| EXPECT_EQ(INVALID_HANDLE_VALUE, Uint32ToHandle(invalid_handle)); |
| } |
| |
| TEST(BaseWinUtilTest, PseudoHandles) { |
| EXPECT_TRUE(IsPseudoHandle(::GetCurrentProcess())); |
| EXPECT_TRUE(IsPseudoHandle(::GetCurrentThread())); |
| EXPECT_FALSE(IsPseudoHandle(nullptr)); |
| } |
| |
| TEST(BaseWinUtilTest, WStringFromGUID) { |
| const GUID kGuid = {0x7698f759, |
| 0xf5b0, |
| 0x4328, |
| {0x92, 0x38, 0xbd, 0x70, 0x8a, 0x6d, 0xc9, 0x63}}; |
| const std::wstring_view kGuidStr = L"{7698F759-F5B0-4328-9238-BD708A6DC963}"; |
| auto guid_wstring = WStringFromGUID(kGuid); |
| EXPECT_EQ(guid_wstring, kGuidStr); |
| wchar_t guid_wchar[39]; |
| ::StringFromGUID2(kGuid, guid_wchar, std::size(guid_wchar)); |
| EXPECT_STREQ(guid_wstring.c_str(), guid_wchar); |
| ScopedCoMem<OLECHAR> clsid_string; |
| ::StringFromCLSID(kGuid, &clsid_string); |
| EXPECT_STREQ(guid_wstring.c_str(), clsid_string.get()); |
| } |
| |
| TEST(BaseWinUtilTest, GetWindowObjectName) { |
| std::wstring created_desktop_name(L"test_desktop"); |
| HDESK desktop_handle = |
| ::CreateDesktop(created_desktop_name.c_str(), nullptr, nullptr, 0, |
| DESKTOP_CREATEWINDOW | DESKTOP_READOBJECTS | |
| READ_CONTROL | WRITE_DAC | WRITE_OWNER, |
| nullptr); |
| |
| ASSERT_NE(desktop_handle, nullptr); |
| EXPECT_EQ(created_desktop_name, GetWindowObjectName(desktop_handle)); |
| ASSERT_TRUE(::CloseDesktop(desktop_handle)); |
| } |
| |
| TEST(BaseWinUtilTest, IsRunningUnderDesktopName) { |
| HDESK thread_desktop = ::GetThreadDesktop(::GetCurrentThreadId()); |
| |
| ASSERT_NE(thread_desktop, nullptr); |
| std::wstring desktop_name = GetWindowObjectName(thread_desktop); |
| |
| EXPECT_TRUE(IsRunningUnderDesktopName(desktop_name)); |
| EXPECT_TRUE(IsRunningUnderDesktopName( |
| AsWString(ToLowerASCII(AsStringPiece16(desktop_name))))); |
| EXPECT_TRUE(IsRunningUnderDesktopName( |
| AsWString(ToUpperASCII(AsStringPiece16(desktop_name))))); |
| EXPECT_FALSE( |
| IsRunningUnderDesktopName(desktop_name + L"_non_existent_desktop_name")); |
| } |
| |
| TEST(BaseWinUtilTest, ExpandEnvironmentVariables) { |
| constexpr char kTestEnvVar[] = "TEST_ENV_VAR"; |
| constexpr char kTestEnvVarValue[] = "TEST_VALUE"; |
| ScopedEnvironmentVariableOverride scoped_env(kTestEnvVar, kTestEnvVarValue); |
| |
| auto path_with_env_var = UTF8ToWide(std::string("C:\\%") + kTestEnvVar + "%"); |
| auto path_expanded = UTF8ToWide(std::string("C:\\") + kTestEnvVarValue); |
| |
| EXPECT_EQ(ExpandEnvironmentVariables(path_with_env_var).value(), |
| path_expanded); |
| } |
| |
| TEST(BaseWinUtilTest, ExpandEnvironmentVariablesEmptyValue) { |
| constexpr char kTestEnvVar[] = "TEST_ENV_VAR"; |
| constexpr char kTestEnvVarValue[] = ""; |
| ScopedEnvironmentVariableOverride scoped_env(kTestEnvVar, kTestEnvVarValue); |
| |
| auto path_with_env_var = UTF8ToWide(std::string("C:\\%") + kTestEnvVar + "%"); |
| auto path_expanded = UTF8ToWide(std::string("C:\\") + kTestEnvVarValue); |
| |
| EXPECT_EQ(ExpandEnvironmentVariables(path_with_env_var).value(), |
| path_expanded); |
| } |
| |
| TEST(BaseWinUtilTest, ExpandEnvironmentVariablesUndefinedValue) { |
| constexpr char kTestEnvVar[] = "TEST_ENV_VAR"; |
| |
| auto path_with_env_var = UTF8ToWide(std::string("C:\\%") + kTestEnvVar + "%"); |
| |
| // Undefined env vars are left unexpanded. |
| auto path_expanded = path_with_env_var; |
| |
| EXPECT_EQ(ExpandEnvironmentVariables(path_with_env_var).value(), |
| path_expanded); |
| } |
| |
| TEST(BaseWinUtilTest, ProcessPowerThrottling) { |
| if (GetVersion() < Version::WIN11_22H2) { |
| GTEST_SKIP() << "Test only applies to Windows 11 22H2 and later."; |
| } |
| |
| // Clear any previous state. |
| ASSERT_TRUE( |
| SetProcessEcoQoSState(::GetCurrentProcess(), ProcessPowerState::kUnset)); |
| ASSERT_TRUE(SetProcessTimerThrottleState(::GetCurrentProcess(), |
| ProcessPowerState::kUnset)); |
| |
| // Verify the initial state. |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kUnset); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kUnset); |
| |
| // Verify setting the EcoQoS state. |
| ASSERT_TRUE(SetProcessEcoQoSState(::GetCurrentProcess(), |
| ProcessPowerState::kEnabled)); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| ASSERT_TRUE( |
| SetProcessEcoQoSState(::GetCurrentProcess(), ProcessPowerState::kUnset)); |
| |
| // Verify setting the timer resolution state. |
| ASSERT_TRUE(SetProcessTimerThrottleState(::GetCurrentProcess(), |
| ProcessPowerState::kEnabled)); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| |
| // Set the EcoQoS state again and verify the timer throttling state is not |
| // clobbered. |
| ASSERT_TRUE(SetProcessEcoQoSState(::GetCurrentProcess(), |
| ProcessPowerState::kEnabled)); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| |
| // Disable the EcoQoS state and verify the timer throttling state is not |
| // clobbered. |
| ASSERT_TRUE(SetProcessEcoQoSState(::GetCurrentProcess(), |
| ProcessPowerState::kDisabled)); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kDisabled); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| |
| // Disable the timer throttling state and verify state. |
| ASSERT_TRUE(SetProcessTimerThrottleState(::GetCurrentProcess(), |
| ProcessPowerState::kDisabled)); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kDisabled); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kDisabled); |
| |
| // Enable both states and verify. |
| ASSERT_TRUE(SetProcessEcoQoSState(::GetCurrentProcess(), |
| ProcessPowerState::kEnabled)); |
| ASSERT_TRUE(SetProcessTimerThrottleState(::GetCurrentProcess(), |
| ProcessPowerState::kEnabled)); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kEnabled); |
| |
| // Clear both states and verify. |
| ASSERT_TRUE( |
| SetProcessEcoQoSState(::GetCurrentProcess(), ProcessPowerState::kUnset)); |
| ASSERT_TRUE(SetProcessTimerThrottleState(::GetCurrentProcess(), |
| ProcessPowerState::kUnset)); |
| ASSERT_TRUE(GetProcessEcoQoSState(::GetCurrentProcess()) == |
| ProcessPowerState::kUnset); |
| ASSERT_TRUE(GetProcessTimerThrottleState(::GetCurrentProcess()) == |
| ProcessPowerState::kUnset); |
| } |
| |
| TEST(GetObjectTypeNameTest, NullHandle) { |
| auto name_or_error = GetObjectTypeName(kNullProcessHandle); |
| ASSERT_FALSE(name_or_error.has_value()); |
| ASSERT_EQ(name_or_error.error(), STATUS_INVALID_HANDLE); |
| } |
| |
| TEST(GetObjectTypeNameTest, InvalidHandle) { |
| auto name_or_error = GetObjectTypeName(INVALID_HANDLE_VALUE); |
| ASSERT_FALSE(name_or_error.has_value()); |
| ASSERT_EQ(name_or_error.error(), STATUS_INVALID_HANDLE); |
| } |
| |
| TEST(GetObjectTypeNameTest, CurrentProcess) { |
| auto name_or_error = GetObjectTypeName(::GetCurrentProcess()); |
| ASSERT_FALSE(name_or_error.has_value()); |
| ASSERT_EQ(name_or_error.error(), STATUS_INVALID_HANDLE); |
| } |
| |
| TEST(GetObjectTypeNameDeathTest, CrazyHandle) { |
| EXPECT_DEATH_IF_SUPPORTED( |
| std::ignore = GetObjectTypeName(Uint32ToHandle(0x12345678U)), |
| "Received fatal exception 0xc0000008"); |
| } |
| |
| TEST(GetObjectTypeNameTest, ProcessHandle) { |
| Process this_process = Process::Open(GetCurrentProcId()); |
| ASSERT_OK_AND_ASSIGN(std::wstring type_name, |
| GetObjectTypeName(this_process.Handle())); |
| ASSERT_EQ(type_name, L"Process"); |
| } |
| |
| TEST(DeviceConvertibilityTest, None) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper(false, false, csm_false, |
| std::nullopt, std::nullopt); |
| EXPECT_FALSE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ConvertibilityDisabled) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| // If convertibility is not enabled but the key exists, other values shouldn't |
| // be checked. Device is not convertible. |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/true, /*chassis_convertible=*/true, |
| /*csm_changed=*/csm_false, /*convertible_chassis_key=*/std::nullopt, |
| /*convertibility_enabled=*/std::optional<bool>{false}); |
| EXPECT_FALSE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ConvertibilityEnabled) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/false, /*chassis_convertible=*/false, |
| /*csm_changed=*/csm_false, /*convertible_chassis_key=*/std::nullopt, |
| /*convertibility_enabled=*/std::optional<bool>{true}); |
| EXPECT_TRUE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ChassisConvertibleKeyTrue) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/false, /*chassis_convertible=*/false, |
| /*csm_changed=*/csm_false, |
| /*convertible_chassis_key=*/std::optional<bool>{true}, |
| /*convertibility_enabled=*/std::nullopt); |
| EXPECT_TRUE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ChassisConvertibleKeyFalse) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/false, /*chassis_convertible=*/true, |
| /*csm_changed=*/csm_true, |
| /*convertible_chassis_key=*/std::optional<bool>{false}, |
| /*convertibility_enabled=*/std::nullopt); |
| EXPECT_FALSE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, FormConvertibleTrue) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/true, /*chassis_convertible=*/false, |
| /*csm_changed=*/csm_false, |
| /*convertible_chassis_key=*/std::nullopt, |
| /*convertibility_enabled=*/std::nullopt); |
| EXPECT_TRUE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ChassisConvertibleTrue) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/false, /*chassis_convertible=*/true, |
| /*csm_changed=*/csm_false, |
| /*convertible_chassis_key=*/std::nullopt, |
| /*convertibility_enabled=*/std::nullopt); |
| EXPECT_TRUE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ConvertibleSlateModeChangeTrue) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| ScopedDeviceConvertibilityStateForTesting scoper( |
| /*form_convertible=*/false, /*chassis_convertible=*/false, |
| /*csm_changed=*/csm_true, |
| /*convertible_chassis_key=*/std::nullopt, |
| /*convertibility_enabled=*/std::nullopt); |
| EXPECT_TRUE(QueryDeviceConvertibility()); |
| } |
| |
| TEST(DeviceConvertibilityTest, ConvertibilityEnabledSanityCheck) { |
| RegKey key(HKEY_LOCAL_MACHINE, |
| L"System\\CurrentControlSet\\Control\\PriorityControl", KEY_READ); |
| if (key.HasValue(L"ConvertibilityEnabled")) { |
| ASSERT_TRUE(GetConvertibilityEnabledOverride().has_value()); |
| } else { |
| ASSERT_FALSE(GetConvertibilityEnabledOverride().has_value()); |
| } |
| } |
| |
| TEST(DeviceConvertibilityTest, ConvertibilityKeySanityCheck) { |
| RegKey key(HKEY_CURRENT_USER, |
| L"SOFTWARE\\Microsoft\\TabletTip\\ConvertibleChassis", KEY_READ); |
| if (key.HasValue(L"ConvertibleChassis")) { |
| ASSERT_TRUE(GetConvertibleChassisKeyValue().has_value()); |
| } else { |
| ASSERT_FALSE(GetConvertibleChassisKeyValue().has_value()); |
| } |
| } |
| |
| TEST(DeviceConvertibilityTest, DeviceFormAndChassisConvertible) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_TRUE(com_initializer.Succeeded()); |
| EXPECT_FALSE(IsDeviceFormConvertible() || IsChassisConvertible()); |
| } |
| |
| TEST(BaseWinUtilTest, GetSerialNumber) { |
| ScopedCOMInitializer com_initializer; |
| ASSERT_OK_AND_ASSIGN(std::wstring serial_number, GetSerialNumber()); |
| EXPECT_FALSE(serial_number.empty()); |
| } |
| |
| TEST(BaseWinUtilTest, UnicodeStringToView) { |
| UNICODE_STRING nullstr = {}; |
| EXPECT_TRUE(UnicodeStringToView(nullstr).empty()); |
| TestUnicodeStringToView(L""); |
| TestUnicodeStringToView(L"ThisIsATestString"); |
| TestUnicodeStringToView(std::wstring((UINT16_MAX / sizeof(WCHAR)) - 1, L'A')); |
| } |
| |
| TEST(BaseWinUtilTest, ViewToUnicodeString) { |
| TestViewToUnicodeString({}); |
| TestViewToUnicodeString(L""); |
| TestViewToUnicodeString(L"ThisIsATestString"); |
| std::wstring long_str(UINT16_MAX / sizeof(WCHAR), L'A'); |
| TestViewToUnicodeString(long_str); |
| long_str += L"A"; |
| UNICODE_STRING invalid = {}; |
| EXPECT_FALSE(ViewToUnicodeString(long_str, invalid)); |
| } |
| |
| // This policy is set in `TestSuite::Initialize` for all tests so this test |
| // checks that it takes effect here. |
| TEST(BaseWinUtilTest, StrictHandleChecks) { |
| PROCESS_MITIGATION_STRICT_HANDLE_CHECK_POLICY policy = {}; |
| ASSERT_TRUE(::GetProcessMitigationPolicy(::GetCurrentProcess(), |
| ProcessStrictHandleCheckPolicy, |
| &policy, sizeof(policy))); |
| EXPECT_TRUE(policy.HandleExceptionsPermanentlyEnabled); |
| EXPECT_TRUE(policy.RaiseExceptionOnInvalidHandleReference); |
| } |
| |
| } // namespace win |
| } // namespace base |