chromium/chrome/installer/util/install_service_work_item_impl.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/installer/util/install_service_work_item_impl.h"

#include <cguid.h>
#include <guiddef.h>

#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "base/check.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "chrome/installer/util/registry_util.h"

using base::win::RegKey;

namespace installer {

namespace {

constexpr uint32_t kServiceType = SERVICE_WIN32_OWN_PROCESS;
constexpr uint32_t kServiceErrorControl = SERVICE_ERROR_NORMAL;
constexpr wchar_t 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,
};

std::wstring GetComRegistryPath(std::wstring_view hive, const GUID& guid) {
  return base::StrCat(
      {L"Software\\Classes\\", hive, L"\\", base::win::WStringFromGUID(guid)});
}

std::wstring GetComClsidRegistryPath(const GUID& clsid) {
  return GetComRegistryPath(L"CLSID", clsid);
}

std::wstring GetComAppidRegistryPath(const GUID& appid) {
  return GetComRegistryPath(L"AppID", appid);
}

std::wstring GetComIidRegistryPath(const GUID& iid) {
  return GetComRegistryPath(L"Interface", iid);
}

std::wstring GetComTypeLibRegistryPath(const GUID& iid) {
  return GetComRegistryPath(L"TypeLib", iid);
}

}  // namespace

InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig() = default;

InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig(
    uint32_t service_type,
    uint32_t service_start_type,
    uint32_t service_error_control,
    const std::wstring& service_cmd_line,
    const wchar_t* dependencies_multi_sz,
    const std::wstring& service_display_name)
    : 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)),
      display_name(service_display_name) {}

InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig(
    const ServiceConfig& rhs) = default;
InstallServiceWorkItemImpl::ServiceConfig::ServiceConfig(ServiceConfig&& rhs) =
    default;

InstallServiceWorkItemImpl::ServiceConfig::~ServiceConfig() = default;

bool operator==(const InstallServiceWorkItemImpl::ServiceConfig& lhs,
                const InstallServiceWorkItemImpl::ServiceConfig& rhs) {
  return lhs.type == rhs.type && lhs.start_type == rhs.start_type &&
         lhs.error_control == rhs.error_control &&
         !_wcsicmp(lhs.cmd_line.c_str(), rhs.cmd_line.c_str()) &&
         lhs.dependencies == rhs.dependencies &&
         !_wcsicmp(lhs.display_name.c_str(), rhs.display_name.c_str());
}

InstallServiceWorkItemImpl::InstallServiceWorkItemImpl(
    const std::wstring& service_name,
    const std::wstring& display_name,
    uint32_t start_type,
    const base::CommandLine& service_cmd_line,
    const base::CommandLine& com_service_cmd_line_args,
    const std::wstring& registry_path,
    const std::vector<GUID>& clsids,
    const std::vector<GUID>& iids)
    : com_registration_work_items_(WorkItem::CreateWorkItemList()),
      service_name_(service_name),
      display_name_(display_name),
      start_type_(start_type),
      service_cmd_line_(service_cmd_line),
      com_service_cmd_line_args_(com_service_cmd_line_args),
      registry_path_(registry_path),
      clsids_(clsids),
      iids_(iids),
      rollback_existing_service_(false),
      rollback_new_service_(false),
      original_service_still_exists_(false) {}

InstallServiceWorkItemImpl::~InstallServiceWorkItemImpl() = default;

bool InstallServiceWorkItemImpl::DoImpl() {
  return DoInstallService() && DoComRegistration();
}

bool InstallServiceWorkItemImpl::DoInstallService() {
  scm_.Set(::OpenSCManager(nullptr, nullptr,
                           SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE));
  if (!scm_.IsValid()) {
    PLOG(ERROR) << "::OpenSCManager Failed";
    return false;
  }

  if (!OpenService()) {
    VPLOG(1) << "Attempting to install new service following failure to open";
    if (InstallNewService())
      return true;

    PLOG(ERROR) << "Failed to install service "
                << GetCurrentServiceName().c_str();
    // Fall through to try installing the service by generating a new name.
  } else if (UpgradeService()) {
    // 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.
    return true;
  } else {
    LOG(ERROR) << "Failed to upgrade service "
               << GetCurrentServiceName().c_str();
  }

  // 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();
  if (!CreateAndSetServiceName())
    PLOG(ERROR) << "Failed to create and set unique service name";

  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;
  }

  PLOG(ERROR) << "Failed to install service with new name "
              << GetCurrentServiceName().c_str();
  return false;
}

bool InstallServiceWorkItemImpl::DoComRegistration() {
  for (const auto& clsid : clsids_) {
    const std::wstring clsid_reg_path = GetComClsidRegistryPath(clsid);
    const std::wstring appid_reg_path = GetComAppidRegistryPath(clsid);

    com_registration_work_items_->AddCreateRegKeyWorkItem(
        HKEY_LOCAL_MACHINE, clsid_reg_path, WorkItem::kWow64Default);
    com_registration_work_items_->AddSetRegValueWorkItem(
        HKEY_LOCAL_MACHINE, clsid_reg_path, WorkItem::kWow64Default, L"AppID",
        base::win::WStringFromGUID(clsid), true);
    com_registration_work_items_->AddCreateRegKeyWorkItem(
        HKEY_LOCAL_MACHINE, appid_reg_path, WorkItem::kWow64Default);
    com_registration_work_items_->AddSetRegValueWorkItem(
        HKEY_LOCAL_MACHINE, appid_reg_path, WorkItem::kWow64Default,
        L"LocalService", GetCurrentServiceName(), true);

    base::CommandLine::StringType com_service_args_string =
        com_service_cmd_line_args_.GetArgumentsString();
    if (!com_service_args_string.empty()) {
      com_registration_work_items_->AddSetRegValueWorkItem(
          HKEY_LOCAL_MACHINE, appid_reg_path, WorkItem::kWow64Default,
          L"ServiceParameters", com_service_args_string, true);
    } else {
      com_registration_work_items_->AddDeleteRegValueWorkItem(
          HKEY_LOCAL_MACHINE, appid_reg_path, WorkItem::kWow64Default,
          L"ServiceParameters");
    }
  }

  for (const auto& iid : iids_) {
    const std::wstring iid_reg_path = GetComIidRegistryPath(iid);
    const std::wstring typelib_reg_path = GetComTypeLibRegistryPath(iid);
    const std::wstring iid_string = base::win::WStringFromGUID(iid);

    for (const auto& key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
      // Registering the Ole Automation marshaler with the CLSID
      // {00020424-0000-0000-C000-000000000046} as the proxy/stub for the
      // interface.
      {
        const std::wstring path = iid_reg_path + L"\\ProxyStubClsid32";
        com_registration_work_items_->AddCreateRegKeyWorkItem(
            HKEY_LOCAL_MACHINE, path, key_flag);
        com_registration_work_items_->AddSetRegValueWorkItem(
            HKEY_LOCAL_MACHINE, path, key_flag, L"",
            L"{00020424-0000-0000-C000-000000000046}", true);
      }
      {
        const std::wstring path = iid_reg_path + L"\\TypeLib";
        com_registration_work_items_->AddCreateRegKeyWorkItem(
            HKEY_LOCAL_MACHINE, path, key_flag);
        com_registration_work_items_->AddSetRegValueWorkItem(
            HKEY_LOCAL_MACHINE, path, key_flag, L"", iid_string, true);
        com_registration_work_items_->AddSetRegValueWorkItem(
            HKEY_LOCAL_MACHINE, path, key_flag, L"Version", L"1.0", true);
      }
      com_registration_work_items_->AddSetRegValueWorkItem(
          HKEY_LOCAL_MACHINE, iid_reg_path, key_flag, L"",
          base::StrCat({L"Interface ", iid_string}), true);
    }

    // The TypeLib registration for the Ole Automation marshaler.
    for (const auto& path : {typelib_reg_path + L"\\1.0\\0\\win32",
                             typelib_reg_path + L"\\1.0\\0\\win64"}) {
      com_registration_work_items_->AddCreateRegKeyWorkItem(
          HKEY_LOCAL_MACHINE, path, WorkItem::kWow64Default);
      com_registration_work_items_->AddSetRegValueWorkItem(
          HKEY_LOCAL_MACHINE, path, WorkItem::kWow64Default, L"",
          service_cmd_line_.GetProgram().value(), true);
    }
    com_registration_work_items_->AddSetRegValueWorkItem(
        HKEY_LOCAL_MACHINE, typelib_reg_path + L"\\1.0",
        WorkItem::kWow64Default, L"",
        base::StrCat({L"TypeLib for Interface ", iid_string}), true);
  }

  return com_registration_work_items_->Do();
}

void InstallServiceWorkItemImpl::RollbackImpl() {
  com_registration_work_items_->Rollback();

  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);
    RestoreOriginalServiceConfig();
    return;
  }

  DCHECK(rollback_new_service_);
  DCHECK(service_.IsValid());

  // Delete the newly created service.
  DeleteCurrentService();

  if (original_service_name_.empty())
    return;

  if (original_service_still_exists_) {
    // Set only the service name back to original_service_name_ and return.
    if (!SetServiceName(original_service_name_)) {
      PLOG(ERROR) << "Failed to restore service name to "
                  << original_service_name_;
    }
    return;
  }

  // Recreate original service with a new service name to avoid possible SCM
  // issues with reusing the old name.
  if (!CreateAndSetServiceName())
    PLOG(ERROR) << "Failed to create and set unique service name";
  ReinstallOriginalService();
}

bool InstallServiceWorkItemImpl::DeleteServiceImpl() {
  // Uninstall the elevation service.
  for (const auto& clsid : clsids_) {
    for (const auto& reg_path :
         {GetComClsidRegistryPath(clsid), GetComAppidRegistryPath(clsid)}) {
      installer::DeleteRegistryKey(HKEY_LOCAL_MACHINE, reg_path,
                                   WorkItem::kWow64Default);
    }
  }

  for (const auto& iid : iids_) {
    {
      const std::wstring reg_path = GetComIidRegistryPath(iid);
      for (const auto& key_flag : {KEY_WOW64_32KEY, KEY_WOW64_64KEY}) {
        installer::DeleteRegistryKey(HKEY_LOCAL_MACHINE, reg_path, key_flag);
      }
    }
    {
      const std::wstring reg_path = GetComTypeLibRegistryPath(iid);
      installer::DeleteRegistryKey(HKEY_LOCAL_MACHINE, reg_path,
                                   WorkItem::kWow64Default);
    }
  }

  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, registry_path_.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;
}

InstallServiceWorkItemImpl::ServiceConfig
InstallServiceWorkItemImpl::MakeUpgradeServiceConfig(
    const ServiceConfig& original_config) {
  ServiceConfig new_config(kServiceType, start_type_, kServiceErrorControl,
                           service_cmd_line_.GetCommandLineString(),
                           kServiceDependencies,
                           GetCurrentServiceDisplayName());

  if (original_config.type == new_config.type)
    new_config.type = SERVICE_NO_CHANGE;
  if (original_config.start_type == new_config.start_type)
    new_config.start_type = SERVICE_NO_CHANGE;
  if (original_config.error_control == new_config.error_control)
    new_config.error_control = SERVICE_NO_CHANGE;
  if (!_wcsicmp(original_config.cmd_line.c_str(),
                new_config.cmd_line.c_str())) {
    new_config.cmd_line.clear();
  }
  if (original_config.dependencies == new_config.dependencies)
    new_config.dependencies.clear();
  if (!_wcsicmp(original_config.display_name.c_str(),
                new_config.display_name.c_str())) {
    new_config.display_name.clear();
  }

  return new_config;
}

bool InstallServiceWorkItemImpl::IsUpgradeNeeded(
    const ServiceConfig& new_config) {
  ServiceConfig default_config;
  default_config.is_valid = true;
  return !(new_config == default_config);
}

bool InstallServiceWorkItemImpl::ChangeServiceConfig(
    const ServiceConfig& config) {
  DCHECK(service_.IsValid());

  // Change the configuration of the existing service.
  // If the service is deleted, ::ChangeServiceConfig will fail with the error
  // ERROR_SERVICE_MARKED_FOR_DELETE.
  if (!::ChangeServiceConfig(
          service_.Get(), config.type, config.start_type, config.error_control,
          !config.cmd_line.empty() ? config.cmd_line.c_str() : nullptr,
          /*lpLoadOrderGroup=*/nullptr,
          /*lpdwTagId=*/nullptr,
          !config.dependencies.empty() ? config.dependencies.data() : nullptr,
          /*lpServiceStartName=*/nullptr, /*lpPassword=*/nullptr,
          !config.display_name.empty() ? config.display_name.c_str()
                                       : nullptr)) {
    PLOG(WARNING) << "Failed to change service config "
                  << GetCurrentServiceName().c_str();
    return false;
  }

  return true;
}

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)) {
    PLOG(ERROR) << "QueryServiceConfig failed "
                << GetCurrentServiceName().c_str();
    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"",
      service_config->lpDisplayName ? service_config->lpDisplayName : 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 std::wstring versioned_service_name(GenerateVersionedServiceName());
  return SetServiceName(versioned_service_name);
}

bool InstallServiceWorkItemImpl::SetServiceName(
    const std::wstring& service_name) const {
  base::win::RegKey key;

  auto result = key.Create(HKEY_LOCAL_MACHINE, registry_path_.c_str(),
                           KEY_SET_VALUE | KEY_WOW64_32KEY);
  DCHECK(result == ERROR_SUCCESS);
  if (result != ERROR_SUCCESS) {
    ::SetLastError(result);
    PLOG(ERROR) << "key.Open failed";
    return false;
  }

  result = key.WriteValue(service_name_.c_str(), service_name.c_str());
  if (result != ERROR_SUCCESS) {
    ::SetLastError(result);
    PLOG(ERROR) << "key.WriteValue failed";
    return false;
  }

  return true;
}

std::wstring InstallServiceWorkItemImpl::GetCurrentServiceName() const {
  base::win::RegKey key;

  auto result = key.Open(HKEY_LOCAL_MACHINE, registry_path_.c_str(),
                         KEY_QUERY_VALUE | KEY_WOW64_32KEY);
  if (result != ERROR_SUCCESS)
    return service_name_;

  std::wstring versioned_service_name;
  key.ReadValue(service_name_.c_str(), &versioned_service_name);
  return versioned_service_name.empty() ? service_name_
                                        : versioned_service_name;
}

std::wstring InstallServiceWorkItemImpl::GetCurrentServiceDisplayName() const {
  return base::StrCat({display_name_, L" (", GetCurrentServiceName(), L")"});
}

std::vector<wchar_t> InstallServiceWorkItemImpl::MultiSzToVector(
    const wchar_t* multi_sz) {
  if (!multi_sz)
    return std::vector<wchar_t>();

  if (!*multi_sz)
    return std::vector<wchar_t>(1, L'\0');

  // Scan forward to the second terminating '\0' at the end of the list of
  // strings in the multi-sz.
  const wchar_t* scan = multi_sz;
  do {
    scan += wcslen(scan) + 1;
  } while (*scan);

  return std::vector<wchar_t>(multi_sz, scan + 1);
}

bool InstallServiceWorkItemImpl::InstallNewService() {
  DCHECK(!service_.IsValid());
  bool success = InstallService(
      ServiceConfig(kServiceType, start_type_, kServiceErrorControl,
                    service_cmd_line_.GetCommandLineString(),
                    kServiceDependencies, GetCurrentServiceDisplayName()));
  if (success)
    rollback_new_service_ = true;
  return success;
}

bool InstallServiceWorkItemImpl::UpgradeService() {
  DCHECK(service_.IsValid());
  DCHECK(!original_service_config_.is_valid);

  ServiceConfig original_config;
  if (!GetServiceConfig(&original_config))
    return false;

  ServiceConfig new_config = MakeUpgradeServiceConfig(original_config);
  const bool upgrade_needed = IsUpgradeNeeded(new_config);
  if (upgrade_needed) {
    original_service_config_ = std::move(original_config);
  } else {
    // In order to determine whether the Service is correctly installed as
    // opposed to being in a "deleted" state, we attempt to change just the
    // display name in the service configuration even if it is correctly
    // configured.
    new_config.display_name = original_config.display_name;
  }

  // If the service is deleted, `ChangeServiceConfig()` will return false.
  bool success = ChangeServiceConfig(new_config);
  if (success && upgrade_needed)
    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(), config.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()) {
    PLOG(WARNING) << "Failed to create service "
                  << GetCurrentServiceName().c_str();
    return false;
  }

  service_ = std::move(service);
  return true;
}

bool InstallServiceWorkItemImpl::DeleteService(ScopedScHandle service) const {
  if (!service.IsValid())
    return false;

  if (!::DeleteService(service.Get())) {
    DWORD error = ::GetLastError();
    PLOG(WARNING) << "DeleteService failed " << GetCurrentServiceName().c_str();
    return error == ERROR_SERVICE_MARKED_FOR_DELETE;
  }

  return true;
}

std::wstring InstallServiceWorkItemImpl::GenerateVersionedServiceName() const {
  const FILETIME filetime = base::Time::Now().ToFileTime();
  return service_name_ +
         base::ASCIIToWide(base::StringPrintf("%lx%lx", filetime.dwHighDateTime,
                                              filetime.dwLowDateTime));
}

}  // namespace installer