blob: 3d983e89ac6ae09fe7940500a0e29ff8a29a800c [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 <string.h>
#include <memory>
#include <utility>
#include "base/logging.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;
} // 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::string16& service_cmd_line)
: service_name_(service_name),
display_name_(display_name),
service_cmd_line_(service_cmd_line),
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";
return false;
}
if (!OpenService())
return InstallNewService();
// 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)))
original_service_still_exists_ = true;
return true;
}
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);
LOG_IF(WARNING, !RestoreOriginalServiceConfig());
return;
}
DCHECK(rollback_new_service_);
DCHECK(service_.IsValid());
// Delete the newly created service.
// TODO(ganesh): If this Delete fails, there will be an extra service. We need
// to use UMA to record occurrences. And have cleanup code that is run on
// subsequent updates to delete stale services.
LOG_IF(WARNING, !DeleteCurrentService());
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());
LOG_IF(WARNING, !ReinstallOriginalService());
}
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;
LONG 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))
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;
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