blob: 40447d5849e3061ef48f6aeeb3e7c985d4d4df3b [file] [log] [blame]
// 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