blob: 0dc142d19aa71ad5e7e530797ca330ae83b1d9fb [file] [log] [blame]
// Copyright 2018 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 "chrome/installer/setup/install_service_work_item_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "chrome/install_static/install_util.h"
using base::win::RegKey;
namespace installer {
namespace {
constexpr uint32_t kServiceType = SERVICE_WIN32_OWN_PROCESS;
constexpr uint32_t kServiceStartType = SERVICE_DEMAND_START;
constexpr uint32_t kServiceErrorControl = SERVICE_ERROR_NORMAL;
constexpr base::char16 kServiceDependencies[] = L"RPCSS\0";
// For the service handle, all permissions that could possibly be used in all
// Do/Rollback scenarios are requested since the handle is reused.
constexpr uint32_t kServiceAccess =
DELETE | SERVICE_QUERY_CONFIG | SERVICE_CHANGE_CONFIG;
// One value for each possible outcome of DoImpl.
// These values are logged to histograms. Entries should not be renumbered and
// numeric values should never be reused.
enum class ServiceInstallResult {
kFailedFreshInstall = 0,
kFailedInstallNewAfterFailedUpgrade = 1,
kFailedOpenSCManager = 2,
kSucceededChangeServiceConfig = 3,
kSucceededFreshInstall = 4,
kSucceededInstallNewAndDeleteOriginal = 5,
kSucceededInstallNewAndFailedDeleteOriginal = 6,
kSucceededServiceCorrectlyConfigured = 7,
kMaxValue = kSucceededServiceCorrectlyConfigured,
};
// One value for each possible outcome of RollbackImpl.
// These values are logged to histograms. Entries should not be renumbered and
// numeric values should never be reused.
enum class ServiceRollbackResult {
kFailedDeleteCurrentService = 0,
kFailedRollbackOriginalServiceConfig = 1,
kSucceededDeleteCurrentService = 2,
kSucceededRollbackOriginalServiceConfig = 3,
kMaxValue = kSucceededRollbackOriginalServiceConfig,
};
// One value for each possible call to RecordWin32ApiErrorCode. When new values
// are added here, histogram_suffixes "SetupInstallWin32Apis" needs to be
// updated in histograms.xml.
constexpr char kChangeServiceConfig[] = "ChangeServiceConfig";
constexpr char kCreateService[] = "CreateService";
constexpr char kDeleteService[] = "DeleteService";
constexpr char kOpenSCManager[] = "OpenSCManager";
void RecordServiceInstallResult(ServiceInstallResult value) {
// Use the histogram function rather than the macro since only one value will
// be recorded per run.
base::UmaHistogramEnumeration("Setup.Install.ServiceInstallResult", value);
}
void RecordServiceRollbackResult(ServiceRollbackResult value) {
// Uses the histogram function rather than the macro since only one value will
// be recorded per run.
base::UmaHistogramEnumeration("Setup.Install.ServiceRollbackResult", value);
}
// Records the last Win32 error in a histogram named
// "Setup.Install.Win32ApiError.|function|". |function| is one of the values in
// the list of histogram suffixes "SetupInstallWin32Apis" above.
void RecordWin32ApiErrorCode(const char* function) {
auto error_code = ::GetLastError();
// Uses the histogram function rather than the macro since the name of the
// histogram is computed at runtime.
base::UmaHistogramSparse(
base::StrCat({"Setup.Install.Win32ApiError.", function}), error_code);
}
} // namespace
InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig()
: is_valid(false),
type(SERVICE_KERNEL_DRIVER),
start_type(SERVICE_DISABLED),
error_control(SERVICE_ERROR_CRITICAL) {}
InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig(
uint32_t service_type,
uint32_t service_start_type,
uint32_t service_error_control,
const base::string16& service_cmd_line,
const base::char16* dependencies_multi_sz)
: is_valid(true),
type(service_type),
start_type(service_start_type),
error_control(service_error_control),
cmd_line(service_cmd_line),
dependencies(MultiSzToVector(dependencies_multi_sz)) {}
InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig(ServiceConfig&& rhs) =
default;
InstallServiceWorkItemImpl::ServiceConfig::~ServiceConfig() = default;
InstallServiceWorkItemImpl::InstallServiceWorkItemImpl(
const base::string16& service_name,
const base::string16& display_name,
const base::CommandLine& service_cmd_line)
: service_name_(service_name),
display_name_(display_name),
service_cmd_line_(service_cmd_line.GetCommandLineString()),
rollback_existing_service_(false),
rollback_new_service_(false),
original_service_still_exists_(false) {}
InstallServiceWorkItemImpl::~InstallServiceWorkItemImpl() = default;
bool InstallServiceWorkItemImpl::DoImpl() {
scm_.Set(::OpenSCManager(nullptr, nullptr,
SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE));
if (!scm_.IsValid()) {
DPLOG(ERROR) << "::OpenSCManager Failed";
RecordServiceInstallResult(ServiceInstallResult::kFailedOpenSCManager);
RecordWin32ApiErrorCode(kOpenSCManager);
return false;
}
if (!OpenService()) {
const bool succeeded = InstallNewService();
if (succeeded) {
RecordServiceInstallResult(ServiceInstallResult::kSucceededFreshInstall);
} else {
RecordServiceInstallResult(ServiceInstallResult::kFailedFreshInstall);
RecordWin32ApiErrorCode(kCreateService);
}
return succeeded;
}
// It is preferable to do a lightweight upgrade of the existing service,
// instead of deleting and recreating a new service, since it is less
// likely to fail. Less intrusive to the SCM and to AV/Anti-malware programs.
if (UpgradeService())
return true;
// Save the original service name. Then create a new service name so as to not
// conflict with the previous one to be safe, then install the new service.
original_service_name_ = GetCurrentServiceName();
LOG_IF(WARNING, !CreateAndSetServiceName());
ScopedScHandle original_service = std::move(service_);
if (InstallNewService()) {
// Delete the previous version of the service.
if (DeleteService(std::move(original_service))) {
RecordServiceInstallResult(
ServiceInstallResult::kSucceededInstallNewAndDeleteOriginal);
} else {
original_service_still_exists_ = true;
RecordServiceInstallResult(
ServiceInstallResult::kSucceededInstallNewAndFailedDeleteOriginal);
RecordWin32ApiErrorCode(kDeleteService);
}
return true;
}
RecordServiceInstallResult(
ServiceInstallResult::kFailedInstallNewAfterFailedUpgrade);
RecordWin32ApiErrorCode(kCreateService);
return false;
}
void InstallServiceWorkItemImpl::RollbackImpl() {
DCHECK(!(rollback_existing_service_ && rollback_new_service_));
if (!rollback_existing_service_ && !rollback_new_service_)
return;
if (rollback_existing_service_) {
DCHECK(service_.IsValid());
DCHECK(original_service_config_.is_valid);
if (RestoreOriginalServiceConfig()) {
RecordServiceRollbackResult(
ServiceRollbackResult::kSucceededRollbackOriginalServiceConfig);
} else {
RecordServiceRollbackResult(
ServiceRollbackResult::kFailedRollbackOriginalServiceConfig);
RecordWin32ApiErrorCode(kChangeServiceConfig);
}
return;
}
DCHECK(rollback_new_service_);
DCHECK(service_.IsValid());
// Delete the newly created service.
if (DeleteCurrentService()) {
RecordServiceRollbackResult(
ServiceRollbackResult::kSucceededDeleteCurrentService);
} else {
RecordServiceRollbackResult(
ServiceRollbackResult::kFailedDeleteCurrentService);
RecordWin32ApiErrorCode(kDeleteService);
}
if (original_service_name_.empty())
return;
if (original_service_still_exists_) {
// Set only the service name back to original_service_name_ and return.
LOG_IF(WARNING, !SetServiceName(original_service_name_));
return;
}
// Recreate original service with a new service name to avoid possible SCM
// issues with reusing the old name.
LOG_IF(WARNING, !CreateAndSetServiceName());
if (!ReinstallOriginalService())
RecordWin32ApiErrorCode(kCreateService);
}
bool InstallServiceWorkItemImpl::DeleteServiceImpl() {
scm_.Set(::OpenSCManager(nullptr, nullptr, SC_MANAGER_CONNECT));
if (!scm_.IsValid()) {
DPLOG(ERROR) << "::OpenSCManager Failed";
return false;
}
if (!OpenService())
return false;
if (!DeleteCurrentService())
return false;
// If the service cannot be deleted, the service name value is not deleted.
// This is to allow for identifying that an existing instance of the service
// is still installed when a future install or upgrade runs.
base::win::RegKey key;
auto result = key.Open(HKEY_LOCAL_MACHINE,
install_static::GetClientStateKeyPath().c_str(),
KEY_SET_VALUE | KEY_WOW64_32KEY);
if (result != ERROR_SUCCESS)
return result == ERROR_FILE_NOT_FOUND || result == ERROR_PATH_NOT_FOUND;
result = key.DeleteValue(service_name_.c_str());
return result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND ||
result == ERROR_PATH_NOT_FOUND;
}
bool InstallServiceWorkItemImpl::IsServiceCorrectlyConfigured(
const ServiceConfig& config) {
return config.type == kServiceType &&
config.start_type == kServiceStartType &&
config.error_control == kServiceErrorControl &&
!_wcsicmp(config.cmd_line.c_str(), service_cmd_line_.c_str()) &&
config.dependencies == MultiSzToVector(kServiceDependencies);
}
bool InstallServiceWorkItemImpl::DeleteCurrentService() {
return DeleteService(std::move(service_));
}
bool InstallServiceWorkItemImpl::OpenService() {
DCHECK(scm_.IsValid());
service_.Set(::OpenService(scm_.Get(), GetCurrentServiceName().c_str(),
kServiceAccess));
return service_.IsValid();
}
bool InstallServiceWorkItemImpl::GetServiceConfig(ServiceConfig* config) const {
DCHECK(config);
DCHECK(service_.IsValid());
constexpr uint32_t kMaxQueryConfigBufferBytes = 8 * 1024;
// ::QueryServiceConfig expects a buffer of at most 8K bytes, according to
// documentation. While the size of the buffer can be dynamically computed,
// we just assume the maximum size for simplicity.
auto buffer = std::make_unique<uint8_t[]>(kMaxQueryConfigBufferBytes);
DWORD bytes_needed_ignored = 0;
QUERY_SERVICE_CONFIG* service_config =
reinterpret_cast<QUERY_SERVICE_CONFIG*>(buffer.get());
if (!::QueryServiceConfig(service_.Get(), service_config,
kMaxQueryConfigBufferBytes,
&bytes_needed_ignored)) {
DPLOG(ERROR) << "QueryServiceConfig failed";
return false;
}
*config = ServiceConfig(
service_config->dwServiceType, service_config->dwStartType,
service_config->dwErrorControl,
service_config->lpBinaryPathName ? service_config->lpBinaryPathName : L"",
service_config->lpDependencies ? service_config->lpDependencies : L"");
return true;
}
// Creates a unique name of the form "{prefix}1c9b3d6baf90df3" and stores it in
// the registry under HKLM\Google\Chrome. Subsequent invocations of
// GetCurrentServiceName() will return this new value.
// The service_name_ is used as the "Name" entry in the registry under
// HKLM\Software\Google\Update\ClientState\{appguid}. For example,
// HKLM\Software\Google\Update\ClientState\{appguid}
// "Name" "elevationservice", "Type" "REG_SZ", "Data" "elevationservice0394"
bool InstallServiceWorkItemImpl::CreateAndSetServiceName() const {
const base::string16 versioned_service_name(GenerateVersionedServiceName());
return SetServiceName(versioned_service_name);
}
bool InstallServiceWorkItemImpl::SetServiceName(
const base::string16& service_name) const {
base::win::RegKey key;
// This assumes that a WorkItem to create the key has already executed before
// this WorkItem. this is generally true since one is added in
// AddUninstallShortcutWorkItems.
auto result = key.Open(HKEY_LOCAL_MACHINE,
install_static::GetClientStateKeyPath().c_str(),
KEY_SET_VALUE | KEY_WOW64_32KEY);
if (result != ERROR_SUCCESS) {
::SetLastError(result);
DPLOG(ERROR) << "key.Open failed";
return false;
}
result = key.WriteValue(service_name_.c_str(), service_name.c_str());
if (result != ERROR_SUCCESS) {
::SetLastError(result);
DPLOG(ERROR) << "key.WriteValue failed";
return false;
}
return true;
}
base::string16 InstallServiceWorkItemImpl::GetCurrentServiceName() const {
base::win::RegKey key;
auto result = key.Open(HKEY_LOCAL_MACHINE,
install_static::GetClientStateKeyPath().c_str(),
KEY_QUERY_VALUE | KEY_WOW64_32KEY);
if (result != ERROR_SUCCESS)
return service_name_;
base::string16 versioned_service_name;
key.ReadValue(service_name_.c_str(), &versioned_service_name);
return versioned_service_name.empty() ? service_name_
: versioned_service_name;
}
std::vector<base::char16> InstallServiceWorkItemImpl::MultiSzToVector(
const base::char16* multi_sz) {
if (!multi_sz)
return std::vector<base::char16>();
if (!*multi_sz)
return std::vector<base::char16>(1, L'\0');
// Scan forward to the second terminating '\0' at the end of the list of
// strings in the multi-sz.
const base::char16* scan = multi_sz;
do {
scan += wcslen(scan) + 1;
} while (*scan);
return std::vector<base::char16>(multi_sz, scan + 1);
}
bool InstallServiceWorkItemImpl::InstallNewService() {
DCHECK(!service_.IsValid());
bool success = InstallService(
ServiceConfig(kServiceType, kServiceStartType, kServiceErrorControl,
service_cmd_line_, kServiceDependencies));
if (success)
rollback_new_service_ = true;
return success;
}
bool InstallServiceWorkItemImpl::UpgradeService() {
DCHECK(service_.IsValid());
DCHECK(!original_service_config_.is_valid);
ServiceConfig config;
if (!GetServiceConfig(&config))
return false;
if (IsServiceCorrectlyConfigured(config)) {
RecordServiceInstallResult(
ServiceInstallResult::kSucceededServiceCorrectlyConfigured);
return true;
}
original_service_config_ = std::move(config);
bool success = ChangeServiceConfig(
ServiceConfig(kServiceType, kServiceStartType, kServiceErrorControl,
service_cmd_line_, kServiceDependencies));
if (success) {
rollback_existing_service_ = true;
RecordServiceInstallResult(
ServiceInstallResult::kSucceededChangeServiceConfig);
} else {
RecordWin32ApiErrorCode(kChangeServiceConfig);
}
return success;
}
bool InstallServiceWorkItemImpl::ReinstallOriginalService() {
return InstallService(original_service_config_);
}
bool InstallServiceWorkItemImpl::RestoreOriginalServiceConfig() {
return ChangeServiceConfig(original_service_config_);
}
bool InstallServiceWorkItemImpl::InstallService(const ServiceConfig& config) {
ScopedScHandle service(::CreateService(
scm_.Get(), GetCurrentServiceName().c_str(), display_name_.c_str(),
kServiceAccess, config.type, config.start_type, config.error_control,
config.cmd_line.c_str(), nullptr, nullptr,
!config.dependencies.empty() ? config.dependencies.data() : nullptr,
nullptr, nullptr));
if (!service.IsValid()) {
DPLOG(WARNING) << "Failed to create service";
return false;
}
service_ = std::move(service);
return true;
}
bool InstallServiceWorkItemImpl::ChangeServiceConfig(
const ServiceConfig& config) {
DCHECK(service_.IsValid());
// Change the configuration of the existing service.
if (!::ChangeServiceConfig(
service_.Get(), config.type, config.start_type, config.error_control,
config.cmd_line.c_str(), nullptr, nullptr,
!config.dependencies.empty() ? config.dependencies.data() : nullptr,
nullptr, nullptr, nullptr)) {
DPLOG(WARNING) << "Failed to change service config";
return false;
}
return true;
}
bool InstallServiceWorkItemImpl::DeleteService(ScopedScHandle service) const {
if (!service.IsValid())
return false;
if (!::DeleteService(service.Get())) {
DWORD error = ::GetLastError();
DPLOG(WARNING) << "DeleteService failed";
return error == ERROR_SERVICE_MARKED_FOR_DELETE;
}
return true;
}
base::string16 InstallServiceWorkItemImpl::GenerateVersionedServiceName()
const {
const FILETIME filetime = base::Time::Now().ToFileTime();
return base::StringPrintf(L"%ls%x%x", service_name_.c_str(),
filetime.dwHighDateTime, filetime.dwLowDateTime);
}
} // namespace installer