blob: a95d480590f54c1e87178debef111d97d1002006 [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/win/util.h"
#include <aclapi.h>
#include <shlobj.h>
#include <windows.h>
#include <wtsapi32.h>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/numerics/ranges.h"
#include "base/process/process_iterator.h"
#include "base/scoped_native_library.h"
#include "base/strings/strcat.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/win/constants.h"
#include "chrome/updater/win/user_info.h"
namespace updater {
namespace {
// The number of iterations to poll if a process is stopped correctly.
const unsigned int kMaxProcessQueryIterations = 50;
// The sleep time in ms between each poll.
const unsigned int kProcessQueryWaitTimeMs = 100;
} // namespace
NamedObjectAttributes::NamedObjectAttributes() = default;
NamedObjectAttributes::~NamedObjectAttributes() = default;
HRESULT HRESULTFromLastError() {
const auto error_code = ::GetLastError();
return (error_code != NO_ERROR) ? HRESULT_FROM_WIN32(error_code) : E_FAIL;
}
bool IsProcessRunning(const wchar_t* executable) {
base::NamedProcessIterator iter(executable, nullptr);
const base::ProcessEntry* entry = iter.NextProcessEntry();
return entry != nullptr;
}
bool WaitForProcessesStopped(const wchar_t* executable) {
DCHECK(executable);
VLOG(1) << "Wait for processes '" << executable << "'.";
// Wait until the process is completely stopped.
for (unsigned int iteration = 0; iteration < kMaxProcessQueryIterations;
++iteration) {
if (!IsProcessRunning(executable))
return true;
::Sleep(kProcessQueryWaitTimeMs);
}
// The process didn't terminate.
LOG(ERROR) << "Cannot stop process '" << executable << "', timeout.";
return false;
}
// This sets up COM security to allow NetworkService, LocalService, and System
// to call back into the process. It is largely inspired by
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa378987.aspx
// static
bool InitializeCOMSecurity() {
// Create the security descriptor explicitly as follows because
// CoInitializeSecurity() will not accept the relative security descriptors
// returned by ConvertStringSecurityDescriptorToSecurityDescriptor().
const size_t kSidCount = 5;
uint64_t* sids[kSidCount][(SECURITY_MAX_SID_SIZE + sizeof(uint64_t) - 1) /
sizeof(uint64_t)] = {
{}, {}, {}, {}, {},
};
// These are ordered by most interesting ones to try first.
WELL_KNOWN_SID_TYPE sid_types[kSidCount] = {
WinBuiltinAdministratorsSid, // administrator group security identifier
WinLocalServiceSid, // local service security identifier
WinNetworkServiceSid, // network service security identifier
WinSelfSid, // personal account security identifier
WinLocalSystemSid, // local system security identifier
};
// This creates a security descriptor that is equivalent to the following
// security descriptor definition language (SDDL) string:
// O:BAG:BAD:(A;;0x1;;;LS)(A;;0x1;;;NS)(A;;0x1;;;PS)
// (A;;0x1;;;SY)(A;;0x1;;;BA)
// Initialize the security descriptor.
SECURITY_DESCRIPTOR security_desc = {};
if (!::InitializeSecurityDescriptor(&security_desc,
SECURITY_DESCRIPTOR_REVISION))
return false;
DCHECK_EQ(kSidCount, base::size(sids));
DCHECK_EQ(kSidCount, base::size(sid_types));
for (size_t i = 0; i < kSidCount; ++i) {
DWORD sid_bytes = sizeof(sids[i]);
if (!::CreateWellKnownSid(sid_types[i], nullptr, sids[i], &sid_bytes))
return false;
}
// Setup the access control entries (ACE) for COM. You may need to modify
// the access permissions for your application. COM_RIGHTS_EXECUTE and
// COM_RIGHTS_EXECUTE_LOCAL are the minimum access rights required.
EXPLICIT_ACCESS explicit_access[kSidCount] = {};
DCHECK_EQ(kSidCount, base::size(sids));
DCHECK_EQ(kSidCount, base::size(explicit_access));
for (size_t i = 0; i < kSidCount; ++i) {
explicit_access[i].grfAccessPermissions =
COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
explicit_access[i].grfAccessMode = SET_ACCESS;
explicit_access[i].grfInheritance = NO_INHERITANCE;
explicit_access[i].Trustee.pMultipleTrustee = nullptr;
explicit_access[i].Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE;
explicit_access[i].Trustee.TrusteeForm = TRUSTEE_IS_SID;
explicit_access[i].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
explicit_access[i].Trustee.ptstrName = reinterpret_cast<LPTSTR>(sids[i]);
}
// Create an access control list (ACL) using this ACE list, if this succeeds
// make sure to ::LocalFree(acl).
ACL* acl = nullptr;
DWORD acl_result = ::SetEntriesInAcl(base::size(explicit_access),
explicit_access, nullptr, &acl);
if (acl_result != ERROR_SUCCESS || acl == nullptr)
return false;
HRESULT hr = E_FAIL;
// Set the security descriptor owner and group to Administrators and set the
// discretionary access control list (DACL) to the ACL.
if (::SetSecurityDescriptorOwner(&security_desc, sids[0], FALSE) &&
::SetSecurityDescriptorGroup(&security_desc, sids[0], FALSE) &&
::SetSecurityDescriptorDacl(&security_desc, TRUE, acl, FALSE)) {
// Initialize COM. You may need to modify the parameters of
// CoInitializeSecurity() for your application. Note that an
// explicit security descriptor is being passed down.
hr = ::CoInitializeSecurity(
&security_desc, -1, nullptr, nullptr, RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IDENTIFY, nullptr,
EOAC_DISABLE_AAA | EOAC_NO_CUSTOM_MARSHAL, nullptr);
}
::LocalFree(acl);
return SUCCEEDED(hr);
}
HMODULE GetModuleHandleFromAddress(void* address) {
MEMORY_BASIC_INFORMATION mbi = {0};
size_t result = ::VirtualQuery(address, &mbi, sizeof(mbi));
DCHECK_EQ(result, sizeof(mbi));
return static_cast<HMODULE>(mbi.AllocationBase);
}
HMODULE GetCurrentModuleHandle() {
return GetModuleHandleFromAddress(
reinterpret_cast<void*>(&GetCurrentModuleHandle));
}
// The event name saved to the environment variable does not contain the
// decoration added by GetNamedObjectAttributes.
HRESULT CreateUniqueEventInEnvironment(const base::string16& var_name,
bool is_machine,
HANDLE* unique_event) {
DCHECK(unique_event);
const base::string16 event_name = base::ASCIIToUTF16(base::GenerateGUID());
NamedObjectAttributes attr;
GetNamedObjectAttributes(event_name.c_str(), is_machine, &attr);
HRESULT hr = CreateEvent(&attr, unique_event);
if (FAILED(hr))
return hr;
if (!::SetEnvironmentVariable(var_name.c_str(), event_name.c_str())) {
DWORD error = ::GetLastError();
return HRESULT_FROM_WIN32(error);
}
return S_OK;
}
HRESULT OpenUniqueEventFromEnvironment(const base::string16& var_name,
bool is_machine,
HANDLE* unique_event) {
DCHECK(unique_event);
base::char16 event_name[MAX_PATH] = {0};
if (!::GetEnvironmentVariable(var_name.c_str(), event_name,
base::size(event_name))) {
DWORD error = ::GetLastError();
return HRESULT_FROM_WIN32(error);
}
NamedObjectAttributes attr;
GetNamedObjectAttributes(event_name, is_machine, &attr);
*unique_event = ::OpenEvent(EVENT_ALL_ACCESS, false, attr.name.c_str());
if (!*unique_event) {
DWORD error = ::GetLastError();
return HRESULT_FROM_WIN32(error);
}
return S_OK;
}
HRESULT CreateEvent(NamedObjectAttributes* event_attr, HANDLE* event_handle) {
DCHECK(event_handle);
DCHECK(event_attr);
DCHECK(!event_attr->name.empty());
*event_handle = ::CreateEvent(&event_attr->sa,
true, // manual reset
false, // not signaled
event_attr->name.c_str());
if (!*event_handle) {
DWORD error = ::GetLastError();
return HRESULT_FROM_WIN32(error);
}
return S_OK;
}
void GetNamedObjectAttributes(const base::char16* base_name,
bool is_machine,
NamedObjectAttributes* attr) {
DCHECK(base_name);
DCHECK(attr);
attr->name = kGlobalPrefix;
if (!is_machine) {
base::string16 user_sid;
GetProcessUser(nullptr, nullptr, &user_sid);
attr->name += user_sid;
GetCurrentUserDefaultSecurityAttributes(&attr->sa);
} else {
// Grant access to administrators and system.
GetAdminDaclSecurityAttributes(&attr->sa, GENERIC_ALL);
}
attr->name += base_name;
}
bool GetCurrentUserDefaultSecurityAttributes(CSecurityAttributes* sec_attr) {
DCHECK(sec_attr);
CAccessToken token;
if (!token.GetProcessToken(TOKEN_QUERY))
return false;
CSecurityDesc security_desc;
CSid sid_owner;
if (!token.GetOwner(&sid_owner))
return false;
security_desc.SetOwner(sid_owner);
CSid sid_group;
if (!token.GetPrimaryGroup(&sid_group))
return false;
security_desc.SetGroup(sid_group);
CDacl dacl;
if (!token.GetDefaultDacl(&dacl))
return false;
CSid sid_user;
if (!token.GetUser(&sid_user))
return false;
if (!dacl.AddAllowedAce(sid_user, GENERIC_ALL))
return false;
security_desc.SetDacl(dacl);
sec_attr->Set(security_desc);
return true;
}
void GetAdminDaclSecurityDescriptor(CSecurityDesc* sd, ACCESS_MASK accessmask) {
DCHECK(sd);
CDacl dacl;
dacl.AddAllowedAce(Sids::System(), accessmask);
dacl.AddAllowedAce(Sids::Admins(), accessmask);
sd->SetOwner(Sids::Admins());
sd->SetGroup(Sids::Admins());
sd->SetDacl(dacl);
sd->MakeAbsolute();
}
void GetAdminDaclSecurityAttributes(CSecurityAttributes* sec_attr,
ACCESS_MASK accessmask) {
DCHECK(sec_attr);
CSecurityDesc sd;
GetAdminDaclSecurityDescriptor(&sd, accessmask);
sec_attr->Set(sd);
}
base::string16 GetRegistryKeyClientsUpdater() {
return base::ASCIIToUTF16(base::StrCat({CLIENTS_KEY, kUpdaterAppId}));
}
base::string16 GetRegistryKeyClientStateUpdater() {
return base::ASCIIToUTF16(base::StrCat({CLIENT_STATE_KEY, kUpdaterAppId}));
}
int GetDownloadProgress(int64_t downloaded_bytes, int64_t total_bytes) {
if (downloaded_bytes == -1 || total_bytes == -1 || total_bytes == 0)
return -1;
DCHECK_LE(downloaded_bytes, total_bytes);
return 100 *
base::ClampToRange(double{downloaded_bytes} / total_bytes, 0.0, 1.0);
}
// Reads the installer progress from the registry value at:
// {HKLM|HKCU}\Software\Google\Update\ClientState\<appid>\InstallerProgress.
int GetInstallerProgress(const std::string& app_id) {
base::string16 subkey;
if (!base::UTF8ToUTF16(app_id.c_str(), app_id.size(), &subkey)) {
return -1;
}
constexpr REGSAM kRegSam = KEY_READ | KEY_WOW64_32KEY;
base::win::RegKey key(HKEY_CURRENT_USER,
base::ASCIIToUTF16(CLIENT_STATE_KEY).c_str(), kRegSam);
if (key.OpenKey(subkey.c_str(), kRegSam) != ERROR_SUCCESS) {
return -1;
}
DWORD progress = 0;
if (key.ReadValueDW(kRegistryValueInstallerProgress, &progress) !=
ERROR_SUCCESS) {
return -1;
}
return base::ClampToRange(progress, DWORD{0}, DWORD{100});
}
bool DeleteInstallerProgress(const std::string& app_id) {
base::string16 subkey;
if (!base::UTF8ToUTF16(app_id.c_str(), app_id.size(), &subkey)) {
return false;
}
constexpr REGSAM kRegSam = KEY_SET_VALUE | KEY_WOW64_32KEY;
base::win::RegKey key(HKEY_CURRENT_USER,
base::ASCIIToUTF16(CLIENT_STATE_KEY).c_str(), kRegSam);
if (key.OpenKey(subkey.c_str(), kRegSam) != ERROR_SUCCESS) {
return false;
}
return key.DeleteValue(kRegistryValueInstallerProgress) == ERROR_SUCCESS;
}
base::win::ScopedHandle GetUserTokenFromCurrentSessionId() {
base::win::ScopedHandle token_handle;
DWORD bytes_returned = 0;
DWORD* session_id_ptr = nullptr;
if (!::WTSQuerySessionInformation(
WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, WTSSessionId,
reinterpret_cast<LPTSTR*>(&session_id_ptr), &bytes_returned)) {
PLOG(ERROR) << "WTSQuerySessionInformation failed.";
return token_handle;
}
DCHECK_EQ(bytes_returned, sizeof(*session_id_ptr));
DWORD session_id = *session_id_ptr;
::WTSFreeMemory(session_id_ptr);
DVLOG(1) << "::WTSQuerySessionInformation session id: " << session_id;
HANDLE token_handle_raw = nullptr;
if (!::WTSQueryUserToken(session_id, &token_handle_raw)) {
PLOG(ERROR) << "WTSQueryUserToken failed";
return token_handle;
}
token_handle.Set(token_handle_raw);
return token_handle;
}
bool PathOwnedByUser(const base::FilePath& path) {
// TODO(crbug.com/1147094): Implement for Win.
return true;
}
} // namespace updater