#include "chrome/windows_services/service_program/service.h"
#include <objidl.h>
#include <sddl.h>
#include <wrl/module.h>
#include <algorithm>
#include <atomic>
#include <string_view>
#include <type_traits>
#include <utility>
#include "base/auto_reset.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/command_line.h"
#include "base/containers/heap_array.h"
#include "base/debug/crash_logging.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/win/atl.h"
#include "base/win/scoped_com_initializer.h"
#include "chrome/windows_services/service_program/process_wrl_module.h"
#include "chrome/windows_services/service_program/service_delegate.h"
namespace {
std::atomic<Service*> g_instance(nullptr);
constexpr std::string_view kConsoleSwitchName = "console";
constexpr wchar_t kWindowsServiceName[] = L"";
void WINAPI SpuriousServiceControlHandler(DWORD) {}
void HandleSpuriousServiceMain(DWORD argc, const wchar_t* const* argv) {
using ServiceHandle =
std::unique_ptr<SC_HANDLE__, decltype(&::CloseServiceHandle)>;
const wchar_t* service_name = argc && *argv ? *argv : kWindowsServiceName;
if (auto scm_raw = ::OpenSCManager(
nullptr,
SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT)) {
ServiceHandle scm(scm_raw, &::CloseServiceHandle);
if (auto svc_raw =
::OpenService(scm.get(), service_name, SERVICE_QUERY_STATUS)) {
ServiceHandle svc(svc_raw, &::CloseServiceHandle);
SERVICE_STATUS_PROCESS status = {};
DWORD bytes_needed = 0;
if (::QueryServiceStatusEx(svc.get(), SC_STATUS_PROCESS_INFO,
reinterpret_cast<unsigned char*>(&status),
sizeof(status), &bytes_needed)) {
LOG(ERROR) << "Spurious start for " << service_name
<< ". Current state: " << status.dwCurrentState
<< "; pid: " << status.dwProcessId;
} else {
PLOG(ERROR) << "Failed to query " << service_name;
}
} else {
PLOG(ERROR) << "Failed to open " << service_name;
}
} else {
PLOG(ERROR) << "Failed to connect to SCM";
}
if (auto service_status_handle = ::RegisterServiceCtrlHandler(
service_name, &SpuriousServiceControlHandler)) {
SERVICE_STATUS service_status{.dwServiceType = SERVICE_WIN32_OWN_PROCESS,
.dwCurrentState = SERVICE_STOPPED};
::SetServiceStatus(service_status_handle, &service_status);
} else {
PCHECK(false);
}
}
}
Service::Service(ServiceDelegate& delegate) : delegate_(delegate) {
Service* expected = nullptr;
CHECK(g_instance.compare_exchange_strong(expected, this,
std::memory_order_relaxed));
}
Service::~Service() {
Service* expected = this;
CHECK(g_instance.compare_exchange_strong(expected, nullptr,
std::memory_order_relaxed));
}
bool Service::InitWithCommandLine(const base::CommandLine* command_line) {
const base::CommandLine::StringVector args = command_line->GetArgs();
if (!args.empty()) {
LOG(ERROR) << "No positional parameters expected.";
return false;
}
if (command_line->HasSwitch(kConsoleSwitchName)) {
run_routine_ = &Service::RunInteractive;
exit_routine_ = &Service::StopInteractive;
}
return true;
}
int Service::Start() {
delegate_implements_run_ = delegate_->PreRun();
const auto result = (this->*run_routine_)();
delegate_->PostRun();
return result;
}
HRESULT Service::RegisterClassObjects(ServiceDelegate& delegate,
base::OnceClosure on_module_released,
base::HeapArray<DWORD>& cookies) {
auto& module = Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule();
auto factories_or_error = delegate.CreateClassFactories();
if (!factories_or_error.has_value()) {
LOG(ERROR) << "Factory creation failed; hr: " << factories_or_error.error();
return factories_or_error.error();
}
base::HeapArray<FactoryAndClsid>& factories = factories_or_error.value();
auto weak_factories =
base::HeapArray<IClassFactory*>::Uninit(factories.size());
auto class_ids = base::HeapArray<IID>::Uninit(factories.size());
auto new_cookies = base::HeapArray<DWORD>::Uninit(factories.size());
size_t i = 0;
for (auto& factory_and_clsid : factories) {
weak_factories[i] = factory_and_clsid.factory.Get();
class_ids[i] = factory_and_clsid.clsid;
++i;
}
SetModuleReleasedCallback(std::move(on_module_released));
HRESULT hr = module.RegisterCOMObject(
nullptr, class_ids.data(), weak_factories.data(), new_cookies.data(),
static_cast<unsigned int>(factories.size()));
if (FAILED(hr)) {
LOG(ERROR) << "RegisterCOMObject failed; hr: " << hr;
SetModuleReleasedCallback({});
return hr;
}
cookies = std::move(new_cookies);
return hr;
}
void Service::UnregisterClassObjects(base::HeapArray<DWORD>& cookies) {
if (!cookies.empty()) {
SetModuleReleasedCallback({});
const HRESULT hr =
Microsoft::WRL::Module<Microsoft::WRL::OutOfProc>::GetModule()
.UnregisterCOMObject(nullptr, cookies.data(), cookies.size());
if (FAILED(hr)) {
LOG(ERROR) << "UnregisterCOMObject failed; hr: 0x" << std::hex << hr;
}
cookies = base::HeapArray<DWORD>();
}
}
Service& Service::GetInstance() {
return CHECK_DEREF(g_instance.load(std::memory_order_relaxed));
}
int Service::RunAsService() {
static constexpr SERVICE_TABLE_ENTRY dispatch_table[] = {
{const_cast<LPTSTR>(kWindowsServiceName), &Service::ServiceMainEntry},
{nullptr, nullptr}};
if (!::StartServiceCtrlDispatcher(dispatch_table)) {
const auto error = ::GetLastError();
static auto* const crash_key = base::debug::AllocateCrashKeyString(
"Service-DispatcherError", base::debug::CrashKeySize::Size32);
base::debug::SetCrashKeyString(crash_key, base::NumberToString(error));
PLOG(ERROR) << "Failed to connect to the service control manager";
return error;
}
base::AutoLock lock(lock_);
return service_status_.dwWin32ExitCode;
}
void Service::StopService() {
SetServiceStatus(SERVICE_STOPPED);
}
void Service::ServiceMainImpl(const base::CommandLine& command_line) {
base::AutoLock lock(lock_);
service_status_handle_ = ::RegisterServiceCtrlHandler(
kWindowsServiceName, &Service::ServiceControlHandler);
if (service_status_handle_ == nullptr) {
PLOG(ERROR) << "RegisterServiceCtrlHandler failed";
return;
}
base::win::ScopedCOMInitializer com_initializer(
base::win::ScopedCOMInitializer::kMTA);
HRESULT hr = com_initializer.hr();
if (SUCCEEDED(hr)) {
SetServiceStatus(SERVICE_RUNNING);
hr = Run(command_line);
} else {
PLOG(ERROR) << "Failed to initialize COM; hr = 0x" << std::hex << hr;
}
if (FAILED(hr)) {
service_status_.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
service_status_.dwServiceSpecificExitCode = hr;
SetServiceStatus(SERVICE_STOPPED);
} else if (delegate_implements_run_) {
SetServiceStatus(SERVICE_STOPPED);
}
}
int Service::RunInteractive() {
base::WaitableEvent exit_event;
base::AutoReset<raw_ptr<base::WaitableEvent>> reset_stop_event(
&interactive_stop_event_, &exit_event);
base::AutoLock lock(lock_);
if (HRESULT hr = Run(*base::CommandLine::ForCurrentProcess());
FAILED(hr) || delegate_implements_run_) {
return hr;
}
{
base::AutoUnlock unlock(lock_);
exit_event.Wait();
}
return S_OK;
}
void Service::StopInteractive() {
CHECK_DEREF(interactive_stop_event_.get()).Signal();
}
void Service::ServiceControlHandler(DWORD control) {
if (control == SERVICE_CONTROL_STOP) {
GetInstance().OnStopRequested();
}
}
void WINAPI Service::ServiceMainEntry(DWORD argc, wchar_t* argv[]) {
if (Service* instance = g_instance.load(std::memory_order_relaxed)) {
instance->ServiceMainImpl(base::CommandLine(argc, argv));
} else {
HandleSpuriousServiceMain(argc, argv);
}
}
void Service::SetServiceStatus(DWORD state) {
if (service_status_handle_) {
service_status_.dwCurrentState = state;
::SetServiceStatus(service_status_handle_, &service_status_);
if (state == SERVICE_STOPPED) {
service_status_handle_ = nullptr;
}
}
}
HRESULT Service::Run(const base::CommandLine& command_line) {
if (HRESULT hr = InitializeComSecurity(); FAILED(hr)) {
return hr;
}
if (Microsoft::WRL::ComPtr<IGlobalOptions> options; SUCCEEDED(
::CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&options)))) {
options->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN);
}
if (delegate_implements_run_) {
return delegate_->Run(command_line);
}
return RegisterClassObjects(
*delegate_,
base::BindOnce(&Service::OnModuleReleased, base::Unretained(this)),
cookies_);
}
HRESULT Service::InitializeComSecurity() {
CDacl dacl;
constexpr auto com_rights_execute_local =
COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL;
if (!dacl.AddAllowedAce(Sids::System(), com_rights_execute_local) ||
!dacl.AddAllowedAce(Sids::Admins(), com_rights_execute_local) ||
!dacl.AddAllowedAce(Sids::Interactive(), com_rights_execute_local)) {
return E_ACCESSDENIED;
}
CSecurityDesc sd;
sd.SetDacl(dacl);
sd.MakeAbsolute();
sd.SetOwner(Sids::Admins());
sd.SetGroup(Sids::Admins());
return ::CoInitializeSecurity(
const_cast<SECURITY_DESCRIPTOR*>(sd.GetPSECURITY_DESCRIPTOR()), -1,
nullptr, nullptr, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IDENTIFY,
nullptr,
EOAC_DYNAMIC_CLOAKING | EOAC_DISABLE_AAA | EOAC_SECURE_REFS |
EOAC_NO_CUSTOM_MARSHAL,
nullptr);
}
void Service::OnModuleReleased() {
CHECK(!delegate_implements_run_);
base::AutoLock lock(lock_);
UnregisterClassObjects(cookies_);
(this->*exit_routine_)();
}
void Service::OnStopRequested() {
delegate_->OnServiceControlStop();
if (delegate_implements_run_) {
return;
}
base::AutoLock lock(lock_);
UnregisterClassObjects(cookies_);
(this->*exit_routine_)();
}