blob: 2d05af2b81fe81a7d0ff41a71e8c8988250859f8 [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/win_util.h"
#include <aclapi.h>
#include <shlobj.h>
#include <windows.h>
#include <wtsapi32.h>
#include <cstdlib>
#include <memory>
#include <string>
#include "base/callback_helpers.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/cxx17_backports.h"
#include "base/files/file_path.h"
#include "base/guid.h"
#include "base/logging.h"
#include "base/memory/free_deleter.h"
#include "base/process/process_iterator.h"
#include "base/scoped_native_library.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/win/user_info.h"
#include "chrome/updater/win/win_constants.h"
#include "third_party/abseil-cpp/absl/types/optional.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;
class ProcessFilterExplorer : public base::ProcessFilter {
public:
~ProcessFilterExplorer() override = default;
// Overrides for base::ProcessFilter.
bool Includes(const base::ProcessEntry& entry) const override {
return base::EqualsCaseInsensitiveASCII(entry.exe_file(), L"EXPLORER.EXE");
}
};
HRESULT IsUserRunningSplitToken(bool& is_split_token) {
HANDLE token = NULL;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token))
return HRESULTFromLastError();
base::win::ScopedHandle token_holder(token);
TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
DWORD size_returned = 0;
if (!::GetTokenInformation(token_holder.Get(), TokenElevationType,
&elevation_type, sizeof(elevation_type),
&size_returned)) {
return HRESULTFromLastError();
}
is_split_token = elevation_type == TokenElevationTypeFull ||
elevation_type == TokenElevationTypeLimited;
DCHECK(is_split_token || elevation_type == TokenElevationTypeDefault);
return S_OK;
}
HRESULT GetSidIntegrityLevel(PSID sid, MANDATORY_LEVEL* level) {
if (!::IsValidSid(sid))
return E_FAIL;
SID_IDENTIFIER_AUTHORITY* authority = ::GetSidIdentifierAuthority(sid);
if (!authority)
return E_FAIL;
static const SID_IDENTIFIER_AUTHORITY kMandatoryLabelAuth =
SECURITY_MANDATORY_LABEL_AUTHORITY;
if (std::memcmp(authority, &kMandatoryLabelAuth,
sizeof(SID_IDENTIFIER_AUTHORITY))) {
return E_FAIL;
}
PUCHAR count = ::GetSidSubAuthorityCount(sid);
if (!count || *count != 1)
return E_FAIL;
DWORD* rid = ::GetSidSubAuthority(sid, 0);
if (!rid)
return E_FAIL;
if ((*rid & 0xFFF) != 0 || *rid > SECURITY_MANDATORY_PROTECTED_PROCESS_RID)
return E_FAIL;
*level = static_cast<MANDATORY_LEVEL>(*rid >> 12);
return S_OK;
}
// Gets the mandatory integrity level of a process.
// TODO(crbug.com/1233748): consider reusing
// base::GetCurrentProcessIntegrityLevel().
HRESULT GetProcessIntegrityLevel(DWORD process_id, MANDATORY_LEVEL* level) {
HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION, false, process_id);
if (!process)
return HRESULTFromLastError();
base::win::ScopedHandle process_holder(process);
HANDLE token = NULL;
if (!::OpenProcessToken(process_holder.Get(),
TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) {
return HRESULTFromLastError();
}
base::win::ScopedHandle token_holder(token);
DWORD label_size = 0;
if (::GetTokenInformation(token_holder.Get(), TokenIntegrityLevel, nullptr, 0,
&label_size) ||
::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
return E_FAIL;
}
std::unique_ptr<TOKEN_MANDATORY_LABEL, base::FreeDeleter> label(
static_cast<TOKEN_MANDATORY_LABEL*>(std::malloc(label_size)));
if (!::GetTokenInformation(token_holder.Get(), TokenIntegrityLevel,
label.get(), label_size, &label_size)) {
return HRESULTFromLastError();
}
return GetSidIntegrityLevel(label->Label.Sid, level);
}
bool IsExplorerRunningAtMediumOrLower() {
ProcessFilterExplorer filter;
base::ProcessIterator iter(&filter);
while (const base::ProcessEntry* process_entry = iter.NextProcessEntry()) {
MANDATORY_LEVEL level = MandatoryLevelUntrusted;
if (SUCCEEDED(GetProcessIntegrityLevel(process_entry->pid(), &level)) &&
level <= MandatoryLevelMedium) {
return true;
}
}
return false;
}
} // 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 std::wstring& var_name,
UpdaterScope scope,
HANDLE* unique_event) {
DCHECK(unique_event);
const std::wstring event_name = base::ASCIIToWide(base::GenerateGUID());
NamedObjectAttributes attr;
GetNamedObjectAttributes(event_name.c_str(), scope, &attr);
HRESULT hr = CreateEvent(&attr, unique_event);
if (FAILED(hr))
return hr;
if (!::SetEnvironmentVariable(var_name.c_str(), event_name.c_str()))
return HRESULTFromLastError();
return S_OK;
}
HRESULT OpenUniqueEventFromEnvironment(const std::wstring& var_name,
UpdaterScope scope,
HANDLE* unique_event) {
DCHECK(unique_event);
wchar_t event_name[MAX_PATH] = {0};
if (!::GetEnvironmentVariable(var_name.c_str(), event_name,
base::size(event_name))) {
return HRESULTFromLastError();
}
NamedObjectAttributes attr;
GetNamedObjectAttributes(event_name, scope, &attr);
*unique_event = ::OpenEvent(EVENT_ALL_ACCESS, false, attr.name.c_str());
if (!*unique_event)
return HRESULTFromLastError();
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)
return HRESULTFromLastError();
return S_OK;
}
void GetNamedObjectAttributes(const wchar_t* base_name,
UpdaterScope scope,
NamedObjectAttributes* attr) {
DCHECK(base_name);
DCHECK(attr);
attr->name = kGlobalPrefix;
switch (scope) {
case UpdaterScope::kUser: {
std::wstring user_sid;
GetProcessUser(nullptr, nullptr, &user_sid);
attr->name += user_sid;
GetCurrentUserDefaultSecurityAttributes(&attr->sa);
break;
}
case UpdaterScope::kSystem:
// Grant access to administrators and system.
GetAdminDaclSecurityAttributes(&attr->sa, GENERIC_ALL);
break;
}
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);
}
std::wstring GetRegistryKeyClientsUpdater() {
return base::StrCat({CLIENTS_KEY, base::ASCIIToWide(kUpdaterAppId)});
}
std::wstring GetRegistryKeyClientStateUpdater() {
return base::StrCat({CLIENT_STATE_KEY, base::ASCIIToWide(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::clamp(static_cast<double>(downloaded_bytes) / total_bytes,
0.0, 1.0);
}
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;
}
// TODO(crbug.com/1212187): maybe handle filtered tokens.
HRESULT IsUserAdmin(bool& is_user_admin) {
SID_IDENTIFIER_AUTHORITY nt_authority = SECURITY_NT_AUTHORITY;
PSID administrators_group = nullptr;
if (!::AllocateAndInitializeSid(&nt_authority, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
&administrators_group)) {
return HRESULTFromLastError();
}
base::ScopedClosureRunner free_sid(
base::BindOnce([](PSID sid) { ::FreeSid(sid); }, administrators_group));
BOOL is_member = false;
if (!::CheckTokenMembership(NULL, administrators_group, &is_member))
return HRESULTFromLastError();
is_user_admin = is_member;
return S_OK;
}
HRESULT IsUserNonElevatedAdmin(bool& is_user_non_elevated_admin) {
HANDLE token = NULL;
if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_READ, &token))
return HRESULTFromLastError();
is_user_non_elevated_admin = false;
base::win::ScopedHandle token_holder(token);
TOKEN_ELEVATION_TYPE elevation_type = TokenElevationTypeDefault;
DWORD size_returned = 0;
if (::GetTokenInformation(token_holder.Get(), TokenElevationType,
&elevation_type, sizeof(elevation_type),
&size_returned)) {
if (elevation_type == TokenElevationTypeLimited) {
is_user_non_elevated_admin = true;
}
}
return S_OK;
}
HRESULT IsUACOn(bool& is_uac_on) {
// The presence of a split token definitively indicates that UAC is on. But
// the absence of the token does not necessarily indicate that UAC is off.
bool is_split_token = false;
if (SUCCEEDED(IsUserRunningSplitToken(is_split_token)) && is_split_token) {
is_uac_on = true;
return S_OK;
}
is_uac_on = IsExplorerRunningAtMediumOrLower();
return S_OK;
}
HRESULT IsElevatedWithUACOn(bool& is_elevated_with_uac_on) {
bool is_user_admin = false;
if (SUCCEEDED(IsUserAdmin(is_user_admin)) && !is_user_admin) {
is_elevated_with_uac_on = false;
return S_OK;
}
return IsUACOn(is_elevated_with_uac_on);
}
std::string GetUACState() {
std::string s;
bool is_user_admin = false;
if (SUCCEEDED(IsUserAdmin(is_user_admin)))
base::StringAppendF(&s, "IsUserAdmin: %d, ", is_user_admin);
bool is_user_non_elevated_admin = false;
if (SUCCEEDED(IsUserNonElevatedAdmin(is_user_non_elevated_admin))) {
base::StringAppendF(&s, "IsUserNonElevatedAdmin: %d, ",
is_user_non_elevated_admin);
}
bool is_uac_on = false;
if (SUCCEEDED(IsUACOn(is_uac_on)))
base::StringAppendF(&s, "IsUACOn: %d, ", is_uac_on);
bool is_elevated_with_uac_on = false;
if (SUCCEEDED(IsElevatedWithUACOn(is_elevated_with_uac_on))) {
base::StringAppendF(&s, "IsElevatedWithUACOn: %d", is_elevated_with_uac_on);
}
return s;
}
std::wstring GetServiceName(bool is_internal_service) {
std::wstring service_name = GetServiceDisplayName(is_internal_service);
service_name.erase(
std::remove_if(service_name.begin(), service_name.end(), isspace),
service_name.end());
return service_name;
}
std::wstring GetServiceDisplayName(bool is_internal_service) {
return base::StrCat(
{base::ASCIIToWide(PRODUCT_FULLNAME_STRING), L" ",
is_internal_service ? kWindowsInternalServiceName : kWindowsServiceName,
L" ", kUpdaterVersionUtf16});
}
std::wstring GetTaskName(UpdaterScope scope) {
std::wstring task_name = GetTaskDisplayName(scope);
task_name.erase(std::remove_if(task_name.begin(), task_name.end(), isspace),
task_name.end());
return task_name;
}
std::wstring GetTaskDisplayName(UpdaterScope scope) {
return base::StrCat({base::ASCIIToWide(PRODUCT_FULLNAME_STRING), L" Task ",
scope == UpdaterScope::kSystem ? L" System " : L" User ",
kUpdaterVersionUtf16});
}
REGSAM Wow6432(REGSAM access) {
return KEY_WOW64_32KEY | access;
}
} // namespace updater