blob: d5e66976bd69552d3206fc6f11b325d46fe8abfb [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/updater/util/win_util.h"
#include <shellapi.h>
#include <shlobj.h>
#include <windows.h>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/system/sys_info.h"
#include "base/test/test_timeouts.h"
#include "base/win/atl.h"
#include "base/win/scoped_handle.h"
#include "chrome/updater/test_scope.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/unittest_util_win.h"
#include "chrome/updater/win/test/test_executables.h"
#include "chrome/updater/win/test/test_strings.h"
#include "chrome/updater/win/win_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace updater {
TEST(WinUtil, GetDownloadProgress) {
EXPECT_EQ(GetDownloadProgress(0, 50), 0);
EXPECT_EQ(GetDownloadProgress(12, 50), 24);
EXPECT_EQ(GetDownloadProgress(25, 50), 50);
EXPECT_EQ(GetDownloadProgress(50, 50), 100);
EXPECT_EQ(GetDownloadProgress(50, 50), 100);
EXPECT_EQ(GetDownloadProgress(0, -1), -1);
EXPECT_EQ(GetDownloadProgress(-1, -1), -1);
EXPECT_EQ(GetDownloadProgress(50, 0), -1);
}
TEST(WinUtil, GetServiceDisplayName) {
for (const bool is_internal_service : {true, false}) {
EXPECT_EQ(base::StrCat({base::ASCIIToWide(PRODUCT_FULLNAME_STRING), L" ",
is_internal_service ? kWindowsInternalServiceName
: kWindowsServiceName,
L" ", kUpdaterVersionUtf16}),
GetServiceDisplayName(is_internal_service));
}
}
TEST(WinUtil, GetServiceName) {
for (const bool is_internal_service : {true, false}) {
EXPECT_EQ(base::StrCat({base::ASCIIToWide(PRODUCT_FULLNAME_STRING),
is_internal_service ? kWindowsInternalServiceName
: kWindowsServiceName,
kUpdaterVersionUtf16}),
GetServiceName(is_internal_service));
}
}
TEST(WinUtil, BuildMsiCommandLine) {
EXPECT_STREQ(L"", BuildMsiCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
base::FilePath(L"NotMsi.exe"))
.c_str());
EXPECT_STREQ(
L"msiexec arg1 arg2 arg3 REBOOT=ReallySuppress /qn /i \"c:\\my "
L"path\\YesMsi.msi\" /log \"c:\\my path\\YesMsi.msi.log\"",
BuildMsiCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
base::FilePath(L"c:\\my path\\YesMsi.msi"))
.c_str());
EXPECT_STREQ(
L"msiexec arg1 arg2 arg3 INSTALLERDATA=\"c:\\my path\\installer data "
L"file.dat\" REBOOT=ReallySuppress /qn /i \"c:\\my "
L"path\\YesMsi.msi\" /log \"c:\\my path\\YesMsi.msi.log\"",
BuildMsiCommandLine(
std::wstring(L"arg1 arg2 arg3"),
base::FilePath(L"c:\\my path\\installer data file.dat"),
base::FilePath(L"c:\\my path\\YesMsi.msi"))
.c_str());
}
TEST(WinUtil, BuildExeCommandLine) {
EXPECT_STREQ(L"", BuildExeCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
base::FilePath(L"NotExe.msi"))
.c_str());
EXPECT_STREQ(L"\"c:\\my path\\YesExe.exe\" arg1 arg2 arg3",
BuildExeCommandLine(std::wstring(L"arg1 arg2 arg3"), {},
base::FilePath(L"c:\\my path\\YesExe.exe"))
.c_str());
EXPECT_STREQ(
L"\"c:\\my path\\YesExe.exe\" arg1 arg2 arg3 --installerdata=\"c:\\my "
L"path\\installer data file.dat\"",
BuildExeCommandLine(
std::wstring(L"arg1 arg2 arg3"),
base::FilePath(L"c:\\my path\\installer data file.dat"),
base::FilePath(L"c:\\my path\\YesExe.exe"))
.c_str());
}
TEST(WinUtil, ShellExecuteAndWait) {
HResultOr<DWORD> result =
ShellExecuteAndWait(base::FilePath(L"NonExistent.Exe"), {}, {});
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
result = ShellExecuteAndWait(
GetTestProcessCommandLine(GetTestScope()).GetProgram(), {}, {});
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), DWORD{0});
}
TEST(WinUtil, RunElevated) {
// TODO(crbug.com/1314521): Click on UAC prompts in Updater tests that require
// elevation
if (!::IsUserAnAdmin())
return;
const base::CommandLine test_process_cmd_line =
GetTestProcessCommandLine(GetTestScope());
HResultOr<DWORD> result =
RunElevated(test_process_cmd_line.GetProgram(),
test_process_cmd_line.GetArgumentsString());
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result.value(), DWORD{0});
}
namespace {
// Allows access to all authenticated users on the machine.
CSecurityDesc GetEveryoneDaclSecurityDescriptor(ACCESS_MASK accessmask) {
CSecurityDesc sd;
CDacl dacl;
dacl.AddAllowedAce(Sids::System(), accessmask);
dacl.AddAllowedAce(Sids::Admins(), accessmask);
dacl.AddAllowedAce(Sids::Interactive(), accessmask);
sd.SetDacl(dacl);
sd.MakeAbsolute();
return sd;
}
} // namespace
TEST(WinUtil, RunDeElevated_Exe) {
if (!::IsUserAnAdmin() || !IsUACOn())
return;
// Create a shared event to be waited for in this process and signaled in the
// test process to confirm that the test process is running at medium
// integrity.
// The event is created with a security descriptor that allows the medium
// integrity process to signal it.
const std::wstring event_name =
base::StrCat({L"WinUtil.RunDeElevated-",
base::NumberToWString(::GetCurrentProcessId())});
CSecurityAttributes sa(GetEveryoneDaclSecurityDescriptor(GENERIC_ALL));
base::WaitableEvent event(base::win::ScopedHandle(
::CreateEvent(&sa, FALSE, FALSE, event_name.c_str())));
ASSERT_NE(event.handle(), nullptr);
base::CommandLine test_process_cmd_line =
GetTestProcessCommandLine(GetTestScope());
test_process_cmd_line.AppendSwitchNative(kTestEventToSignalIfMediumIntegrity,
event_name);
EXPECT_HRESULT_SUCCEEDED(
RunDeElevated(test_process_cmd_line.GetProgram().value(),
test_process_cmd_line.GetArgumentsString()));
EXPECT_TRUE(event.TimedWait(TestTimeouts::action_max_timeout()));
}
TEST(WinUtil, GetOSVersion) {
absl::optional<OSVERSIONINFOEX> rtl_os_version = GetOSVersion();
ASSERT_NE(rtl_os_version, absl::nullopt);
// Compare to the version from `::GetVersionEx`.
OSVERSIONINFOEX os = {};
os.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
EXPECT_TRUE(::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&os)));
#pragma clang diagnostic pop
EXPECT_EQ(rtl_os_version->dwOSVersionInfoSize, os.dwOSVersionInfoSize);
EXPECT_EQ(rtl_os_version->dwMajorVersion, os.dwMajorVersion);
EXPECT_EQ(rtl_os_version->dwMinorVersion, os.dwMinorVersion);
EXPECT_EQ(rtl_os_version->dwBuildNumber, os.dwBuildNumber);
EXPECT_EQ(rtl_os_version->dwPlatformId, os.dwPlatformId);
EXPECT_STREQ(rtl_os_version->szCSDVersion, os.szCSDVersion);
EXPECT_EQ(rtl_os_version->wServicePackMajor, os.wServicePackMajor);
EXPECT_EQ(rtl_os_version->wServicePackMinor, os.wServicePackMinor);
EXPECT_EQ(rtl_os_version->wSuiteMask, os.wSuiteMask);
EXPECT_EQ(rtl_os_version->wProductType, os.wProductType);
}
TEST(WinUtil, CompareOSVersions_SameAsCurrent) {
absl::optional<OSVERSIONINFOEX> this_os = GetOSVersion();
ASSERT_NE(this_os, absl::nullopt);
EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_EQUAL));
EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_GREATER_EQUAL));
EXPECT_FALSE(CompareOSVersions(this_os.value(), VER_GREATER));
EXPECT_FALSE(CompareOSVersions(this_os.value(), VER_LESS));
EXPECT_TRUE(CompareOSVersions(this_os.value(), VER_LESS_EQUAL));
}
TEST(WinUtil, CompareOSVersions_NewBuildNumber) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
ASSERT_GT(prior_os->dwBuildNumber, 0UL);
--prior_os->dwBuildNumber;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
TEST(WinUtil, CompareOSVersions_NewMajor) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
ASSERT_GT(prior_os->dwMajorVersion, 0UL);
--prior_os->dwMajorVersion;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
TEST(WinUtil, CompareOSVersions_NewMinor) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
// This test only runs if the current OS has a minor version.
if (prior_os->dwMinorVersion >= 1) {
--prior_os->dwMinorVersion;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
}
TEST(WinUtil, CompareOSVersions_NewMajorWithLowerMinor) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
ASSERT_GT(prior_os->dwMajorVersion, 0UL);
--prior_os->dwMajorVersion;
++prior_os->dwMinorVersion;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
TEST(WinUtil, CompareOSVersions_OldMajor) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
++prior_os->dwMajorVersion;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
TEST(WinUtil, CompareOSVersions_OldMajorWithHigherMinor) {
absl::optional<OSVERSIONINFOEX> prior_os = GetOSVersion();
ASSERT_NE(prior_os, absl::nullopt);
// This test only runs if the current OS has a minor version.
if (prior_os->dwMinorVersion >= 1) {
++prior_os->dwMajorVersion;
--prior_os->dwMinorVersion;
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_EQUAL));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER_EQUAL));
EXPECT_FALSE(CompareOSVersions(prior_os.value(), VER_GREATER));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS));
EXPECT_TRUE(CompareOSVersions(prior_os.value(), VER_LESS_EQUAL));
}
}
TEST(WinUtil, IsCOMCallerAdmin) {
HResultOr<bool> is_com_caller_admin = IsCOMCallerAdmin();
ASSERT_TRUE(is_com_caller_admin.has_value());
EXPECT_EQ(is_com_caller_admin.value(), ::IsUserAnAdmin());
}
TEST(WinUtil, EnableSecureDllLoading) {
EXPECT_TRUE(EnableSecureDllLoading());
}
TEST(WinUtil, EnableProcessHeapMetadataProtection) {
EXPECT_TRUE(EnableProcessHeapMetadataProtection());
}
TEST(WinUtil, CreateSecureTempDir) {
absl::optional<base::ScopedTempDir> temp_dir = CreateSecureTempDir();
EXPECT_TRUE(temp_dir);
EXPECT_TRUE(temp_dir->IsValid());
base::FilePath program_files_dir;
EXPECT_TRUE(
base::PathService::Get(base::DIR_PROGRAM_FILES, &program_files_dir));
EXPECT_EQ(program_files_dir.IsParent(temp_dir->GetPath()),
!!::IsUserAnAdmin());
}
TEST(WinUtil, SignalShutdownEvent) {
{
const base::ScopedClosureRunner reset_shutdown_event(
SignalShutdownEvent(GetTestScope()));
// Expect that the legacy GoogleUpdate shutdown event is signaled.
EXPECT_TRUE(IsShutdownEventSignaled(GetTestScope()))
<< "Unexpected shutdown event not signaled";
}
// Expect that the legacy GoogleUpdate shutdown event is invalid now.
EXPECT_FALSE(IsShutdownEventSignaled(GetTestScope()))
<< "Unexpected shutdown event signaled";
}
TEST(WinUtil, StopGoogleUpdateProcesses) {
// TODO(crbug.com/1290496) perhaps some comprehensive tests for
// `StopGoogleUpdateProcesses`?
EXPECT_TRUE(StopGoogleUpdateProcesses(GetTestScope()));
}
} // namespace updater