blob: 4505c131825deabf95488916c98a9743d42065d0 [file] [log] [blame]
// Copyright 2019 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/updater/app/server/win/server.h"
#include <wrl/module.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/win/atl.h"
#include "base/win/registry.h"
#include "base/win/windows_types.h"
#include "chrome/installer/util/self_cleaning_temp_dir.h"
#include "chrome/installer/util/work_item_list.h"
#include "chrome/updater/app/server/win/com_classes.h"
#include "chrome/updater/app/server/win/com_classes_legacy.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_service_internal.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util.h"
#include "chrome/updater/win/setup/setup_util.h"
#include "chrome/updater/win/setup/uninstall.h"
#include "chrome/updater/win/win_constants.h"
#include "chrome/updater/win/win_util.h"
#include "components/prefs/pref_service.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace updater {
namespace {
std::wstring GetCOMGroup(const std::wstring& prefix, UpdaterScope scope) {
return base::StrCat({prefix, base::ASCIIToWide(UpdaterScopeToString(scope))});
}
std::wstring COMGroup(UpdaterScope scope) {
return GetCOMGroup(L"Active", scope);
}
std::wstring COMGroupInternal(UpdaterScope scope) {
return GetCOMGroup(L"Internal", scope);
}
// Update the registry value for the "UninstallCmdLine" under the UPDATER_KEY.
bool SwapUninstallCmdLine(UpdaterScope scope,
const base::FilePath& updater_path,
HKEY root,
WorkItemList* list) {
DCHECK(list);
base::CommandLine uninstall_if_unused_command(updater_path);
// TODO(crbug.com/1270520) - use a switch that can uninstall immediately if
// unused, instead of requiring server starts.
uninstall_if_unused_command.AppendSwitch(kUninstallIfUnusedSwitch);
if (scope == UpdaterScope::kSystem)
uninstall_if_unused_command.AppendSwitch(kSystemSwitch);
uninstall_if_unused_command.AppendSwitch(kEnableLoggingSwitch);
uninstall_if_unused_command.AppendSwitchASCII(kLoggingModuleSwitch,
kLoggingModuleSwitchValue);
list->AddSetRegValueWorkItem(
root, UPDATER_KEY, KEY_WOW64_32KEY, kRegValueUninstallCmdLine,
uninstall_if_unused_command.GetCommandLineString(), true);
return true;
}
bool CreateSecureTempDir(UpdaterScope scope,
installer::SelfCleaningTempDir& temp_path) {
base::FilePath temp_dir;
if (!base::PathService::Get(scope == UpdaterScope::kSystem
? int{base::DIR_PROGRAM_FILES}
: base::DIR_TEMP,
&temp_dir)) {
return false;
}
temp_dir = temp_dir.AppendASCII(COMPANY_SHORTNAME_STRING)
.AppendASCII(PRODUCT_FULLNAME_STRING);
if (!temp_path.Initialize(temp_dir, L"UPDATER_TEMP_DIR")) {
PLOG(ERROR) << "Could not create temporary path.";
return false;
}
VLOG(2) << "Created temp path " << temp_path.path().value();
return true;
}
HRESULT AddAllowedAce(HANDLE object,
SE_OBJECT_TYPE object_type,
const CSid& sid,
ACCESS_MASK required_permissions,
UINT8 required_ace_flags) {
CDacl dacl;
if (!AtlGetDacl(object, object_type, &dacl)) {
return HRESULTFromLastError();
}
int ace_count = dacl.GetAceCount();
for (int i = 0; i < ace_count; ++i) {
CSid sid_entry;
ACCESS_MASK existing_permissions = 0;
BYTE existing_ace_flags = 0;
dacl.GetAclEntry(i, &sid_entry, &existing_permissions, NULL,
&existing_ace_flags);
if (sid_entry == sid &&
required_permissions == (existing_permissions & required_permissions) &&
required_ace_flags == (existing_ace_flags & ~INHERITED_ACE)) {
return S_OK;
}
}
if (!dacl.AddAllowedAce(sid, required_permissions, required_ace_flags) ||
!AtlSetDacl(object, object_type, dacl)) {
return HRESULTFromLastError();
}
return S_OK;
}
bool CreateClientStateMedium() {
base::win::RegKey key;
LONG result = key.Create(HKEY_LOCAL_MACHINE, CLIENT_STATE_MEDIUM_KEY,
Wow6432(KEY_WRITE));
if (result != ERROR_SUCCESS) {
VLOG(2) << __func__ << " failed: CreateKey returned " << result;
return false;
}
// Authenticated non-admins may read, write, create subkeys and values.
// The override privileges apply to all subkeys and values but not to the
// ClientStateMedium key itself.
HRESULT hr = AddAllowedAce(
key.Handle(), SE_REGISTRY_KEY, Sids::Interactive(),
KEY_READ | KEY_SET_VALUE | KEY_CREATE_SUB_KEY,
CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE);
if (FAILED(hr)) {
VLOG(2) << __func__ << " failed: AddAllowedAce returned " << hr;
return false;
}
return true;
}
// Install Updater.exe as GoogleUpdate.exe in the file system under
// Google\Update. And add a "pv" registry value under the
// UPDATER_KEY\Clients\{GoogleUpdateAppId}.
// Finally, update the registry value for the "UninstallCmdLine".
bool SwapGoogleUpdate(UpdaterScope scope,
const base::FilePath& updater_path,
const base::FilePath& temp_path,
HKEY root,
WorkItemList* list) {
DCHECK(list);
// TODO(crbug.com/1290496) Do we need to set the shutdown event and wait or
// kill any running GoogleUpdate.exe instances? If so, is waiting a good idea
// during the swap?
const absl::optional<base::FilePath> target_path =
GetGoogleUpdateExePath(scope);
if (!target_path)
return false;
list->AddCopyTreeWorkItem(updater_path, *target_path, temp_path,
WorkItem::ALWAYS);
const std::wstring google_update_appid_key =
GetAppClientsKey(L"{430FD4D0-B729-4F61-AA34-91526481799D}");
list->AddCreateRegKeyWorkItem(root, COMPANY_KEY, KEY_WOW64_32KEY);
list->AddCreateRegKeyWorkItem(root, UPDATER_KEY, KEY_WOW64_32KEY);
list->AddCreateRegKeyWorkItem(root, CLIENTS_KEY, KEY_WOW64_32KEY);
list->AddCreateRegKeyWorkItem(root, google_update_appid_key, KEY_WOW64_32KEY);
list->AddSetRegValueWorkItem(root, google_update_appid_key, KEY_WOW64_32KEY,
kRegValuePV, kUpdaterVersionUtf16, true);
list->AddSetRegValueWorkItem(
root, google_update_appid_key, KEY_WOW64_32KEY, kRegValueName,
base::ASCIIToWide(PRODUCT_FULLNAME_STRING), true);
return SwapUninstallCmdLine(scope, updater_path, root, list);
}
} // namespace
// Returns a leaky singleton of the App instance.
scoped_refptr<ComServerApp> AppServerSingletonInstance() {
return AppSingletonInstance<ComServerApp>();
}
ComServerApp::ComServerApp() = default;
ComServerApp::~ComServerApp() = default;
void ComServerApp::Stop() {
VLOG(2) << __func__ << ": COM server is shutting down.";
UnregisterClassObjects();
main_task_runner_->PostTask(FROM_HERE, base::BindOnce([]() {
scoped_refptr<ComServerApp> this_server =
AppServerSingletonInstance();
this_server->update_service_ = nullptr;
this_server->update_service_internal_ = nullptr;
this_server->Shutdown(0);
}));
}
HRESULT ComServerApp::RegisterClassObjects() {
// Register COM class objects that are under either the ActiveSystem or the
// ActiveUser group.
// See wrl_classes.cc for details on the COM classes within the group.
return Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
.RegisterObjects(COMGroup(updater_scope()).c_str());
}
HRESULT ComServerApp::RegisterInternalClassObjects() {
// Register COM class objects that are under either the InternalSystem or the
// InternalUser group.
// See wrl_classes.cc for details on the COM classes within the group.
return Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
.RegisterObjects(COMGroupInternal(updater_scope()).c_str());
}
void ComServerApp::UnregisterClassObjects() {
const HRESULT hr =
Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
.UnregisterObjects();
LOG_IF(ERROR, FAILED(hr)) << "UnregisterObjects failed; hr: " << hr;
}
void ComServerApp::CreateWRLModule() {
Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::Create(
this, &ComServerApp::Stop);
}
void ComServerApp::ActiveDuty(scoped_refptr<UpdateService> update_service) {
update_service_ = update_service;
Start(base::BindOnce(&ComServerApp::RegisterClassObjects,
base::Unretained(this)));
}
void ComServerApp::ActiveDutyInternal(
scoped_refptr<UpdateServiceInternal> update_service_internal) {
update_service_internal_ = update_service_internal;
Start(base::BindOnce(&ComServerApp::RegisterInternalClassObjects,
base::Unretained(this)));
}
void ComServerApp::Start(base::OnceCallback<HRESULT()> register_callback) {
main_task_runner_ = base::SequencedTaskRunnerHandle::Get();
CreateWRLModule();
HRESULT hr = std::move(register_callback).Run();
if (FAILED(hr))
Shutdown(hr);
}
void ComServerApp::UninstallSelf() {
UninstallCandidate(updater_scope());
}
bool ComServerApp::SwapInNewVersion() {
std::unique_ptr<WorkItemList> list(WorkItem::CreateWorkItemList());
const absl::optional<base::FilePath> versioned_directory =
GetVersionedDataDirectory(updater_scope());
if (!versioned_directory)
return false;
const base::FilePath updater_path =
versioned_directory->Append(GetExecutableRelativePath());
HKEY root = (updater_scope() == UpdaterScope::kSystem) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER;
installer::SelfCleaningTempDir temp_dir;
if (!CreateSecureTempDir(updater_scope(), temp_dir)) {
return false;
}
if (updater_scope() == UpdaterScope::kSystem && !CreateClientStateMedium()) {
return false;
}
if (!SwapGoogleUpdate(updater_scope(), updater_path, temp_dir.path(), root,
list.get())) {
return false;
}
if (updater_scope() == UpdaterScope::kSystem) {
AddComServiceWorkItems(updater_path, false, list.get());
} else {
for (const CLSID& clsid : GetActiveServers(updater_scope())) {
AddInstallServerWorkItems(root, clsid, updater_path, false, list.get());
}
for (const GUID& iid : GetActiveInterfaces()) {
AddInstallComInterfaceWorkItems(root, updater_path, iid, list.get());
}
}
return list->Do();
}
bool ComServerApp::MigrateLegacyUpdaters(
base::RepeatingCallback<void(const RegistrationRequest&)>
register_callback) {
HKEY root = (updater_scope() == UpdaterScope::kSystem) ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER;
for (base::win::RegistryKeyIterator it(root, CLIENTS_KEY, KEY_WOW64_32KEY);
it.Valid(); ++it) {
const std::wstring app_id = it.Name();
// Skip importing legacy updater.
if (base::EqualsCaseInsensitiveASCII(app_id, kLegacyGoogleUpdaterAppID))
continue;
base::win::RegKey key;
if (key.Open(root, GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ)) !=
ERROR_SUCCESS) {
continue;
}
RegistrationRequest registration;
registration.app_id = base::SysWideToUTF8(app_id);
std::wstring pv;
if (key.ReadValue(kRegValuePV, &pv) != ERROR_SUCCESS)
continue;
registration.version = base::Version(base::SysWideToUTF8(pv));
if (!registration.version.IsValid())
continue;
std::wstring brand_code;
if (key.ReadValue(kRegValueBrandCode, &brand_code) == ERROR_SUCCESS)
registration.brand_code = base::SysWideToUTF8(brand_code);
std::wstring ap;
if (key.ReadValue(kRegValueAP, &ap) == ERROR_SUCCESS)
registration.ap = base::SysWideToUTF8(ap);
register_callback.Run(registration);
}
return true;
}
} // namespace updater