Commit e2c2fc96 authored by Yusuf Sengul's avatar Yusuf Sengul Committed by Commit Bot

Add bare GCPW extension service implementation

Adds the logic that handshakes with the service control manager
and runs the process as a service.

Bug: 1101590
Change-Id: Id0825d3ea5bbfccb17147740eda26bff04367037
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2329045Reviewed-by: default avatarRakesh Soma <rakeshsoma@google.com>
Commit-Queue: Yusuf Sengul <yusufsn@google.com>
Cr-Commit-Position: refs/heads/master@{#797476}
parent 5bccb059
...@@ -43,10 +43,24 @@ source_set("common") { ...@@ -43,10 +43,24 @@ source_set("common") {
configs += [ "//build/config/win:windowed" ] configs += [ "//build/config/win:windowed" ]
} }
source_set("extension_lib") {
sources = [
"service.cc",
"service.h",
]
deps = [
":common",
"../gaiacp:common",
"//base:base",
]
configs += [ "//build/config/win:windowed" ]
}
executable("gcpw_extension") { executable("gcpw_extension") {
sources = [ "extension_main.cc" ] sources = [ "extension_main.cc" ]
deps = [ deps = [
":common", ":common",
":extension_lib",
":extension_resources", ":extension_resources",
":version", ":version",
"../eventlog:gcp_eventlog_messages", "../eventlog:gcp_eventlog_messages",
......
...@@ -9,8 +9,11 @@ ...@@ -9,8 +9,11 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/process/memory.h" #include "base/process/memory.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/win/process_startup_helper.h" #include "base/win/process_startup_helper.h"
#include "chrome/credential_provider/eventlog/gcp_eventlog_messages.h" #include "chrome/credential_provider/eventlog/gcp_eventlog_messages.h"
#include "chrome/credential_provider/extension/os_service_manager.h"
#include "chrome/credential_provider/extension/service.h"
#include "chrome/credential_provider/gaiacp/logging.h" #include "chrome/credential_provider/gaiacp/logging.h"
int APIENTRY wWinMain(HINSTANCE hInstance, int APIENTRY wWinMain(HINSTANCE hInstance,
...@@ -48,7 +51,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance, ...@@ -48,7 +51,10 @@ int APIENTRY wWinMain(HINSTANCE hInstance,
// Set the event logging source and category for GCPW Extension. // Set the event logging source and category for GCPW Extension.
logging::SetEventSource("GCPW", GCPW_EXTENSION_CATEGORY, MSG_LOG_MESSAGE); logging::SetEventSource("GCPW", GCPW_EXTENSION_CATEGORY, MSG_LOG_MESSAGE);
LOGFN(VERBOSE) << "GCPW Extension started running."; // This initializes and starts ThreadPoolInstance with default params.
base::ThreadPoolInstance::CreateAndStartWithDefaultParams("gcpw_extension");
credential_provider::extension::Service::Get()->Run();
return 0; return 0;
} }
// Copyright 2020 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/credential_provider/extension/service.h"
#include "base/logging.h"
#include "chrome/credential_provider/extension/os_service_manager.h"
#include "chrome/credential_provider/gaiacp/logging.h"
namespace credential_provider {
namespace extension {
// static
Service** Service::GetInstanceStorage() {
static Service* instance = new Service();
return &instance;
}
// static
Service* Service::Get() {
return *GetInstanceStorage();
}
DWORD Service::Run() {
return (this->*run_routine_)();
}
Service::Service()
: run_routine_(&Service::RunAsService),
service_status_(),
service_status_handle_(nullptr),
stop_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED) {
service_status_.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
service_status_.dwCurrentState = SERVICE_STOPPED;
service_status_.dwControlsAccepted =
SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PRESHUTDOWN;
}
Service::~Service() {}
DWORD Service::RunAsService() {
LOGFN(INFO);
DWORD error_code =
extension::OSServiceManager::Get()->StartServiceCtrlDispatcher(
&Service::ServiceMain);
if (error_code != ERROR_SUCCESS) {
LOGFN(ERROR)
<< "OSServiceManager::StartServiceCtrlDispatcher failed with win32="
<< error_code;
}
return error_code;
}
void Service::StartMain() {
DWORD error_code = extension::OSServiceManager::Get()->RegisterCtrlHandler(
&Service::ServiceControlHandler, &service_status_handle_);
if (error_code != ERROR_SUCCESS) {
LOGFN(ERROR) << "OSServiceManager::RegisterCtrlHandler failed win32="
<< error_code;
return;
}
service_status_.dwCurrentState = SERVICE_RUNNING;
error_code = extension::OSServiceManager::Get()->SetServiceStatus(
service_status_handle_, service_status_);
if (error_code != ERROR_SUCCESS) {
LOGFN(ERROR) << "OSServiceManager::SetServiceStatus failed win32="
<< error_code;
return;
}
stop_event_.Wait();
service_status_.dwCurrentState = SERVICE_STOPPED;
service_status_.dwControlsAccepted = 0;
error_code = extension::OSServiceManager::Get()->SetServiceStatus(
service_status_handle_, service_status_);
if (error_code != ERROR_SUCCESS)
LOGFN(ERROR) << "OSServiceManager::SetServiceStatus failed win32="
<< error_code;
}
// static
VOID WINAPI Service::ServiceMain(DWORD argc /*unused*/,
WCHAR* argv[] /*unused*/) {
LOGFN(INFO);
Service* self = Service::Get();
// Run the service.
self->StartMain();
}
// static
VOID WINAPI Service::ServiceControlHandler(DWORD control) {
LOGFN(INFO);
Service* self = Service::Get();
switch (control) {
case SERVICE_CONTROL_PRESHUTDOWN:
case SERVICE_CONTROL_STOP:
self->service_status_.dwCurrentState = SERVICE_STOP_PENDING;
extension::OSServiceManager::Get()->SetServiceStatus(
self->service_status_handle_, self->service_status_);
self->stop_event_.Signal();
break;
default:
break;
}
}
} // namespace extension
} // namespace credential_provider
// Copyright 2020 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.
#ifndef CHROME_CREDENTIAL_PROVIDER_EXTENSION_SERVICE_H_
#define CHROME_CREDENTIAL_PROVIDER_EXTENSION_SERVICE_H_
#include <windows.h>
#include "base/macros.h"
#include "base/synchronization/waitable_event.h"
namespace credential_provider {
namespace extension {
// Bare implementation of the GCPW extension service. Takes care the handshake
// with the service control manager and the lifetime of the service.
class Service {
public:
// Gets the singleton instance of the service.
static Service* Get();
// Invoke the chosen action routine. By default service runs as a service,
// but the action routine can support running in console for testing purposes.
DWORD Run();
private:
Service();
virtual ~Service();
// The action routine to be executed.
DWORD (Service::*run_routine_)();
// This function handshakes with the service control manager and starts
// the service.
DWORD RunAsService();
// Non-static function that is called as part of service main(ServiceMain).
// Performs registering control handler callback and managing the service
// states.
void StartMain();
// Service main call back which was provided earlier to service control
// manager as part of RunAsService call.
static VOID WINAPI ServiceMain(DWORD argc, WCHAR* argv[]);
// The control handler of the service. Details about the control codes can be
// found here:
// https://docs.microsoft.com/en-us/windows/win32/services/service-control-handler-function
static void WINAPI ServiceControlHandler(DWORD control);
// Returns the storage used for the instance pointer.
static Service** GetInstanceStorage();
// Status of the running service. Must be updated accordingly before calling
// SetServiceStatus API.
SERVICE_STATUS service_status_;
// The service status handle which is used with SetServiceStatus API.
SERVICE_STATUS_HANDLE service_status_handle_;
// Primitive that controls when to finish running service main.
base::WaitableEvent stop_event_;
DISALLOW_COPY_AND_ASSIGN(Service);
};
} // namespace extension
} // namespace credential_provider
#endif // CHROME_CREDENTIAL_PROVIDER_EXTENSION_SERVICE_H_
// Copyright 2020 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 <windows.h>
#include "base/files/file_path.h"
#include "base/threading/thread.h"
#include "chrome/credential_provider/extension/scoped_handle.h"
#include "chrome/credential_provider/extension/service.h"
#include "chrome/credential_provider/test/gcp_fakes.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace credential_provider {
namespace testing {
class GCPWServiceTest : public ::testing::Test {
public:
// Start the service as if SCM is starting it. Note that this will be started
// in a new thread which simulates the main thread when service process
// starts.
void StartServiceMainThread() {
main_thread->Start();
main_thread->task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&GCPWServiceTest::RunService, base::Unretained(this)));
// Give some time so that service starts running and accpeting control
// requests.
::Sleep(1000);
}
void CheckServiceStatus(DWORD status) {
SERVICE_STATUS service_status;
fake_os_service_manager_.GetServiceStatus(&service_status);
ASSERT_EQ(service_status.dwCurrentState, status);
}
void SendControlRequest(DWORD control_request) {
fake_os_service_manager_.SendControlRequestForTesting(control_request);
// Give some time for control to be processed by the service control
// handler.
::Sleep(1000);
}
FakeOSServiceManager* fake_os_service_manager() {
return &fake_os_service_manager_;
}
protected:
void SetUp() override {
main_thread = std::make_unique<base::Thread>("ProcessMain Thread");
}
void TearDown() override {
SendControlRequest(SERVICE_CONTROL_STOP);
main_thread->Stop();
}
private:
void RunService() {
ASSERT_TRUE(credential_provider::extension::Service::Get()->Run() ==
ERROR_SUCCESS);
}
std::unique_ptr<base::Thread> main_thread;
FakeOSServiceManager fake_os_service_manager_;
};
TEST_F(GCPWServiceTest, StartSuccess) {
// Install service into SCM database.
credential_provider::extension::ScopedScHandle sc_handle;
ASSERT_TRUE(ERROR_SUCCESS == fake_os_service_manager()->InstallService(
base::FilePath(L"test"), &sc_handle));
StartServiceMainThread();
CheckServiceStatus(SERVICE_RUNNING);
SendControlRequest(SERVICE_CONTROL_STOP);
CheckServiceStatus(SERVICE_STOPPED);
}
TEST_F(GCPWServiceTest, NoOpControlRequest) {
// Install service into SCM database.
credential_provider::extension::ScopedScHandle sc_handle;
ASSERT_TRUE(ERROR_SUCCESS == fake_os_service_manager()->InstallService(
base::FilePath(L"test"), &sc_handle));
StartServiceMainThread();
CheckServiceStatus(SERVICE_RUNNING);
SendControlRequest(SERVICE_CONTROL_PAUSE);
CheckServiceStatus(SERVICE_RUNNING);
}
} // namespace testing
} // namespace credential_provider
...@@ -7,6 +7,7 @@ import("//testing/test.gni") ...@@ -7,6 +7,7 @@ import("//testing/test.gni")
test("gcp_unittests") { test("gcp_unittests") {
sources = [ sources = [
"../extension/service_unittests.cc",
"../gaiacp/associated_user_validator_unittests.cc", "../gaiacp/associated_user_validator_unittests.cc",
"../gaiacp/device_policies_manager_unittests.cc", "../gaiacp/device_policies_manager_unittests.cc",
"../gaiacp/gaia_credential_base_unittests.cc", "../gaiacp/gaia_credential_base_unittests.cc",
...@@ -32,6 +33,7 @@ test("gcp_unittests") { ...@@ -32,6 +33,7 @@ test("gcp_unittests") {
deps = [ deps = [
"../extension:common", "../extension:common",
"../extension:extension_lib",
"../gaiacp:common", "../gaiacp:common",
"../gaiacp:gaia_credential_provider_idl", "../gaiacp:gaia_credential_provider_idl",
"../gaiacp:gaiacp_lib", "../gaiacp:gaiacp_lib",
......
...@@ -19,9 +19,11 @@ ...@@ -19,9 +19,11 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/synchronization/waitable_event.h" #include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
#include "base/win/scoped_handle.h" #include "base/win/scoped_handle.h"
#include "base/win/scoped_process_information.h" #include "base/win/scoped_process_information.h"
#include "chrome/credential_provider/common/gcp_strings.h" #include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/extension/extension_strings.h"
#include "chrome/credential_provider/gaiacp/gcp_utils.h" #include "chrome/credential_provider/gaiacp/gcp_utils.h"
#include "chrome/credential_provider/gaiacp/logging.h" #include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/mdm_utils.h" #include "chrome/credential_provider/gaiacp/mdm_utils.h"
...@@ -1266,13 +1268,94 @@ FakeOSServiceManager::~FakeOSServiceManager() { ...@@ -1266,13 +1268,94 @@ FakeOSServiceManager::~FakeOSServiceManager() {
*GetInstanceStorage() = os_service_manager_; *GetInstanceStorage() = os_service_manager_;
} }
DWORD FakeOSServiceManager::GetServiceStatus(SERVICE_STATUS* service_status) { DWORD FakeOSServiceManager::StartServiceCtrlDispatcher(
return ERROR_SERVICE_DOES_NOT_EXIST; LPSERVICE_MAIN_FUNCTION service_main) {
if (service_lookup_from_name_.find(extension::kGCPWExtensionServiceName) ==
service_lookup_from_name_.end()) {
return ERROR_INVALID_DATA;
}
LOGFN(INFO);
// Windows calls the service main by creating a new thread. This is simulated
// in the test by creating a new thread.
base::Thread t("ServiceMain Thread");
t.Start();
t.task_runner()->PostTask(FROM_HERE,
base::BindOnce(service_main, 0, nullptr));
while (true) {
// Service looks for control requests so that it calls the service's control
// handler.
DWORD control_request = GetControlRequestForTesting();
LOGFN(INFO) << "Received control: " << control_request;
// This is a custom control to end the service process main when service is
// supposed to stop.
if (control_request == 100)
break;
service_lookup_from_name_[extension::kGCPWExtensionServiceName]
.control_handler_cb_(control_request);
}
return ERROR_SUCCESS;
}
DWORD FakeOSServiceManager::RegisterCtrlHandler(
LPHANDLER_FUNCTION handler_proc,
SERVICE_STATUS_HANDLE* service_status_handle) {
if (service_lookup_from_name_.find(extension::kGCPWExtensionServiceName) ==
service_lookup_from_name_.end()) {
return ERROR_SERVICE_DOES_NOT_EXIST;
}
service_lookup_from_name_[extension::kGCPWExtensionServiceName]
.control_handler_cb_ = handler_proc;
// Set some random integer here. Not needed in the tests.
*service_status_handle = (SERVICE_STATUS_HANDLE)1;
return ERROR_SUCCESS;
}
DWORD FakeOSServiceManager::SetServiceStatus(
SERVICE_STATUS_HANDLE service_status_handle,
SERVICE_STATUS service) {
LOGFN(INFO) << "Service state: " << service.dwCurrentState;
if (service_lookup_from_name_.find(extension::kGCPWExtensionServiceName) ==
service_lookup_from_name_.end()) {
return ERROR_SERVICE_DOES_NOT_EXIST;
}
service_lookup_from_name_[extension::kGCPWExtensionServiceName]
.service_status_ = service;
if (service.dwCurrentState == SERVICE_STOPPED)
SendControlRequestForTesting(100);
return ERROR_SUCCESS;
} }
DWORD FakeOSServiceManager::InstallService( DWORD FakeOSServiceManager::InstallService(
const base::FilePath& service_binary_path, const base::FilePath& service_binary_path,
extension::ScopedScHandle* sc_handle) { extension::ScopedScHandle* sc_handle) {
LOGFN(INFO);
service_lookup_from_name_[extension::kGCPWExtensionServiceName]
.service_status_.dwCurrentState = SERVICE_STOPPED;
return ERROR_SUCCESS;
}
DWORD FakeOSServiceManager::GetServiceStatus(SERVICE_STATUS* service_status) {
LOGFN(INFO);
if (service_lookup_from_name_.find(extension::kGCPWExtensionServiceName) ==
service_lookup_from_name_.end()) {
return ERROR_SERVICE_DOES_NOT_EXIST;
}
*service_status =
service_lookup_from_name_[extension::kGCPWExtensionServiceName]
.service_status_;
return ERROR_SUCCESS;
}
DWORD FakeOSServiceManager::DeleteService() {
service_lookup_from_name_.erase(extension::kGCPWExtensionServiceName);
return ERROR_SUCCESS; return ERROR_SUCCESS;
} }
......
...@@ -6,12 +6,15 @@ ...@@ -6,12 +6,15 @@
#define CHROME_CREDENTIAL_PROVIDER_TEST_GCP_FAKES_H_ #define CHROME_CREDENTIAL_PROVIDER_TEST_GCP_FAKES_H_
#include <deque> #include <deque>
#include <list>
#include <map> #include <map>
#include <memory> #include <memory>
#include <string> #include <string>
#include <thread>
#include <vector> #include <vector>
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_reg_util_win.h" #include "base/test/test_reg_util_win.h"
#include "base/win/scoped_handle.h" #include "base/win/scoped_handle.h"
#include "chrome/credential_provider/extension/os_service_manager.h" #include "chrome/credential_provider/extension/os_service_manager.h"
...@@ -660,8 +663,47 @@ class FakeOSServiceManager : public extension::OSServiceManager { ...@@ -660,8 +663,47 @@ class FakeOSServiceManager : public extension::OSServiceManager {
DWORD InstallService(const base::FilePath& service_binary_path, DWORD InstallService(const base::FilePath& service_binary_path,
extension::ScopedScHandle* sc_handle) override; extension::ScopedScHandle* sc_handle) override;
DWORD StartServiceCtrlDispatcher(
LPSERVICE_MAIN_FUNCTION service_main) override;
DWORD RegisterCtrlHandler(
LPHANDLER_FUNCTION handler_proc,
SERVICE_STATUS_HANDLE* service_status_handle) override;
DWORD SetServiceStatus(SERVICE_STATUS_HANDLE service_status_handle,
SERVICE_STATUS service) override;
void SendControlRequestForTesting(DWORD control_request) {
std::unique_lock<std::mutex> lock(m);
queue.push_back(control_request);
cv.notify_one();
}
DWORD DeleteService() override;
private: private:
DWORD GetControlRequestForTesting() {
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&]() { return !queue.empty(); });
DWORD result = queue.front();
queue.pop_front();
return result;
}
struct ServiceInfo {
LPHANDLER_FUNCTION control_handler_cb_;
SERVICE_STATUS service_status_;
};
// Primitives that are used to synchronize with the thread running service
// main and the thread testing the code.
std::list<DWORD> queue;
std::mutex m;
std::condition_variable cv;
// Original instance of OSServiceManager.
extension::OSServiceManager* os_service_manager_ = nullptr; extension::OSServiceManager* os_service_manager_ = nullptr;
std::map<base::string16, ServiceInfo> service_lookup_from_name_;
}; };
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment