refactor: Move Windows SendSAS call to new thread
Moving the SAS event wait and SendSAS call prevents blocking the main thread allowing for more responsive watchdog (e.g. faster restart Core client/server on session switch).
This commit is contained in:
parent
38f8159e9d
commit
a099276e4e
4 changed files with 127 additions and 51 deletions
48
src/lib/platform/MSWindowsHandle.h
Normal file
48
src/lib/platform/MSWindowsHandle.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Deskflow -- mouse and keyboard sharing utility
|
||||||
|
* SPDX-FileCopyrightText: (C) 2025 Symless Ltd.
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "base/Log.h"
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RAII pattern for a Windows handle (ensures handle is closed when out of scope).
|
||||||
|
*/
|
||||||
|
class MSWindowsHandle
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit MSWindowsHandle(HANDLE handle) : m_handle(handle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
MSWindowsHandle(const MSWindowsHandle &) = delete;
|
||||||
|
MSWindowsHandle &operator=(const MSWindowsHandle &) = delete;
|
||||||
|
MSWindowsHandle(MSWindowsHandle &&) = delete;
|
||||||
|
MSWindowsHandle &operator=(MSWindowsHandle &&) = delete;
|
||||||
|
|
||||||
|
~MSWindowsHandle()
|
||||||
|
{
|
||||||
|
if (m_handle == nullptr || m_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CloseHandle(m_handle)) {
|
||||||
|
LOG_WARN("failed to close handle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE
|
||||||
|
get() const
|
||||||
|
{
|
||||||
|
return m_handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
HANDLE m_handle = nullptr;
|
||||||
|
};
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
/*
|
/*
|
||||||
* Deskflow -- mouse and keyboard sharing utility
|
* Deskflow -- mouse and keyboard sharing utility
|
||||||
* SPDX-FileCopyrightText: (C) 2012 - 2016 Symless Ltd.
|
* SPDX-FileCopyrightText: (C) 2012 - 2022, 2025 Symless Ltd.
|
||||||
* SPDX-FileCopyrightText: (C) 2003 Chris Schoeneman
|
* SPDX-FileCopyrightText: (C) 2003 Chris Schoeneman
|
||||||
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
* SPDX-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "platform/MSWindowsKeyState.h"
|
#include "platform/MSWindowsKeyState.h"
|
||||||
|
|
||||||
#include "arch/win32/ArchMiscWindows.h"
|
#include "arch/win32/XArchWindows.h"
|
||||||
#include "base/FunctionJob.h"
|
|
||||||
#include "base/IEventQueue.h"
|
#include "base/IEventQueue.h"
|
||||||
#include "base/Log.h"
|
#include "base/Log.h"
|
||||||
#include "base/TMethodEventJob.h"
|
|
||||||
#include "mt/Thread.h"
|
|
||||||
#include "platform/MSWindowsDesks.h"
|
#include "platform/MSWindowsDesks.h"
|
||||||
|
#include "platform/MSWindowsHandle.h"
|
||||||
|
|
||||||
// extended mouse buttons
|
// extended mouse buttons
|
||||||
#if !defined(VK_XBUTTON1)
|
#if !defined(VK_XBUTTON1)
|
||||||
|
|
@ -750,26 +748,39 @@ bool MSWindowsKeyState::fakeKeyRepeat(
|
||||||
return KeyState::fakeKeyRepeat(id, mask, count, button, lang);
|
return KeyState::fakeKeyRepeat(id, mask, count, button, lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We must use SendSAS (Secure Attention Sequence) to simulate Ctrl+Alt+Del (since Windows Vista).
|
||||||
|
//
|
||||||
|
// According to the MS docs:
|
||||||
|
// > To successfully call the SendSAS function, an application must either be running as a
|
||||||
|
// > service or have the uiAccess attribute of the requestedExecutionLevel element set to "true"
|
||||||
|
// > in its application manifest.
|
||||||
|
//
|
||||||
|
// Since the daemon is running as a service, we can use it to send the event.
|
||||||
|
// We do this by calling `SetEvent` on the `SendSAS` event (created by the daemon).
|
||||||
|
//
|
||||||
|
// History: It used to be possible to use `PostMessage` to send `MOD_CONTROL | MOD_ALT, VK_DELETE`
|
||||||
|
// as a backup but this ability was removed by Microsoft for security in favor of requiring the
|
||||||
|
// `SendSAS` event to be used, which makes DoS and social engineering attacks more difficult.
|
||||||
bool MSWindowsKeyState::fakeCtrlAltDel()
|
bool MSWindowsKeyState::fakeCtrlAltDel()
|
||||||
{
|
{
|
||||||
// We must use SendSAS (Secure Attention Sequence) to simulate Ctrl+Alt+Del as of Windows Vista.
|
// The daemon creates this event with default permissions, which means this process must be
|
||||||
// The SoftwareSASGeneration registry key must be set for this to work:
|
// elevated to be able to open it. If not elevated, an access denied error will be returned.
|
||||||
// Set-ItemProperty -Path HKLM:Software\Microsoft\Windows\CurrentVersion\Policies\System
|
MSWindowsHandle sendSasEvent(OpenEvent(EVENT_MODIFY_STATE, FALSE, kSendSasEventName));
|
||||||
// -Name SoftwareSASGeneration -Value 1
|
if (!sendSasEvent.get()) {
|
||||||
//
|
XArchEvalWindows error;
|
||||||
// History: It used to be possible to use `PostMessage` to send `MOD_CONTROL | MOD_ALT, VK_DELETE`
|
LOG_ERR("couldn't open SAS event, unable to simulate ctrl+alt+del, error: %s", error.eval().c_str());
|
||||||
// as a backup but this ability was removed by Microsoft for security in favor of requiring the
|
|
||||||
// `SendSAS` event to be used, which makes DoS and social engineering attacks more difficult.
|
|
||||||
HANDLE hSendSasEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, kSendSasEventName);
|
|
||||||
if (hSendSasEvent) {
|
|
||||||
LOG_DEBUG("found SendSAS event, simulating ctrl+alt+del");
|
|
||||||
SetEvent(hSendSasEvent);
|
|
||||||
CloseHandle(hSendSasEvent);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
LOG_ERR("couldn't find SendSAS event, unable to simulate ctrl+alt+del");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: We don't directly call SendSAS, but rather we tell the daemon to do it by setting the event.
|
||||||
|
LOG_DEBUG("setting SAS event to simulate ctrl+alt+del");
|
||||||
|
if (!SetEvent(sendSasEvent.get())) {
|
||||||
|
XArchEvalWindows error;
|
||||||
|
LOG_ERR("failed to set SAS event, unable to simulate ctrl+alt+del, error: %s", error.eval().c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
KeyModifierMask MSWindowsKeyState::pollActiveModifiers() const
|
KeyModifierMask MSWindowsKeyState::pollActiveModifiers() const
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
#include "base/log_outputters.h"
|
#include "base/log_outputters.h"
|
||||||
#include "deskflow/App.h"
|
#include "deskflow/App.h"
|
||||||
#include "mt/Thread.h"
|
#include "mt/Thread.h"
|
||||||
|
#include "platform/MSWindowsHandle.h"
|
||||||
|
|
||||||
#include <Shellapi.h>
|
#include <Shellapi.h>
|
||||||
#include <UserEnv.h>
|
#include <UserEnv.h>
|
||||||
|
|
@ -86,24 +87,28 @@ MSWindowsWatchdog::MSWindowsWatchdog(bool foreground) : m_foreground(foreground)
|
||||||
|
|
||||||
void MSWindowsWatchdog::startAsync()
|
void MSWindowsWatchdog::startAsync()
|
||||||
{
|
{
|
||||||
m_thread = new Thread(new TMethodJob<MSWindowsWatchdog>(this, &MSWindowsWatchdog::mainLoop, nullptr));
|
m_mainThread = std::make_unique<Thread>(new TMethodJob(this, &MSWindowsWatchdog::mainLoop, nullptr));
|
||||||
|
m_outputThread = std::make_unique<Thread>(new TMethodJob(this, &MSWindowsWatchdog::outputLoop, nullptr));
|
||||||
m_outputThread = new Thread(new TMethodJob<MSWindowsWatchdog>(this, &MSWindowsWatchdog::outputLoop, nullptr));
|
m_sasThread = std::make_unique<Thread>(new TMethodJob(this, &MSWindowsWatchdog::sasLoop, nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSWindowsWatchdog::stop()
|
void MSWindowsWatchdog::stop()
|
||||||
{
|
{
|
||||||
|
const auto kThreadWaitSeconds = 5;
|
||||||
|
|
||||||
m_running = false;
|
m_running = false;
|
||||||
|
|
||||||
if (!m_thread->wait(5)) {
|
if (!m_mainThread->wait(kThreadWaitSeconds)) {
|
||||||
LOG((CLOG_WARN "could not stop main thread"));
|
LOG((CLOG_WARN "could not stop main thread"));
|
||||||
}
|
}
|
||||||
delete m_thread;
|
|
||||||
|
|
||||||
if (!m_outputThread->wait(5)) {
|
if (!m_outputThread->wait(kThreadWaitSeconds)) {
|
||||||
LOG((CLOG_WARN "could not stop output thread"));
|
LOG((CLOG_WARN "could not stop output thread"));
|
||||||
}
|
}
|
||||||
delete m_outputThread;
|
|
||||||
|
if (!m_sasThread->wait(kThreadWaitSeconds)) {
|
||||||
|
LOG((CLOG_WARN "could not stop sas thread"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE
|
HANDLE
|
||||||
|
|
@ -232,10 +237,6 @@ void MSWindowsWatchdog::mainLoop(void *)
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
// TODO: This seems like a hack, why would we need to send the SAS function every loop iteration?
|
|
||||||
// This slows down both the process relaunch speed and the watchdog thread loop shut down time.
|
|
||||||
sendSas();
|
|
||||||
|
|
||||||
// Sleep for only 100ms rather than 1 second so that the service can shut down faster.
|
// Sleep for only 100ms rather than 1 second so that the service can shut down faster.
|
||||||
ARCH->sleep(0.1);
|
ARCH->sleep(0.1);
|
||||||
}
|
}
|
||||||
|
|
@ -561,23 +562,36 @@ void MSWindowsWatchdog::initSasFunc()
|
||||||
LOG_DEBUG("found SendSAS function in sas.dll");
|
LOG_DEBUG("found SendSAS function in sas.dll");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MSWindowsWatchdog::sendSas() const
|
void MSWindowsWatchdog::sasLoop(void *) // NOSONAR - Thread entry point signature
|
||||||
{
|
{
|
||||||
if (m_sendSasFunc == nullptr) {
|
if (m_sendSasFunc == nullptr) {
|
||||||
throw XArch("SendSAS function not initialized");
|
throw XArch("SendSAS function not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
HANDLE sendSasEvent = CreateEvent(nullptr, FALSE, FALSE, kSendSasEventName);
|
while (m_running) {
|
||||||
if (sendSasEvent == nullptr) {
|
if (m_processState != ProcessState::Running) {
|
||||||
LOG_ERR("could not create SendSAS event");
|
LOG_DEBUG2("watchdog: not running, skipping SendSAS");
|
||||||
throw XArch(new XArchEvalWindows());
|
ARCH->sleep(1);
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// use SendSAS event to wait for next session (timeout 1 second).
|
// Create a an event so that other processes can tell the daemon to call the `SendSAS` function.
|
||||||
if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) {
|
MSWindowsHandle sendSasEvent(CreateEvent(nullptr, FALSE, FALSE, kSendSasEventName));
|
||||||
LOG_DEBUG("calling SendSAS from sas.dll");
|
if (sendSasEvent.get() == nullptr) {
|
||||||
m_sendSasFunc(FALSE);
|
XArchEvalWindows error;
|
||||||
}
|
LOG_ERR("could not create SAS event, error: %s", error.eval().c_str());
|
||||||
|
ARCH->sleep(1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
CloseHandle(sendSasEvent);
|
// Wait for the Core client to tell the daemon to call the `SendSAS` function.
|
||||||
|
if (WaitForSingleObject(sendSasEvent.get(), 1000) == WAIT_OBJECT_0) {
|
||||||
|
|
||||||
|
// The SoftwareSASGeneration registry key must be set for this to work:
|
||||||
|
// Set-ItemProperty -Path HKLM:Software\Microsoft\Windows\CurrentVersion\Policies\System
|
||||||
|
// -Name SoftwareSASGeneration -Value 1
|
||||||
|
LOG_DEBUG("calling SendSAS to simulate ctrl+alt+del");
|
||||||
|
m_sendSasFunc(FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,6 @@ private:
|
||||||
*/
|
*/
|
||||||
void initSasFunc();
|
void initSasFunc();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Send a SAS (Secure Attention Sequence) for Ctrl+Alt+Del emulation.
|
|
||||||
*/
|
|
||||||
void sendSas() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Re-run the process to get the active desktop name.
|
* @brief Re-run the process to get the active desktop name.
|
||||||
*
|
*
|
||||||
|
|
@ -135,6 +130,13 @@ private:
|
||||||
*/
|
*/
|
||||||
std::string runActiveDesktopUtility();
|
std::string runActiveDesktopUtility();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allows the SendSAS function to be called from other processes.
|
||||||
|
*
|
||||||
|
* SendSAS sends a SAS (Secure Attention Sequence) for Ctrl+Alt+Del emulation.
|
||||||
|
*/
|
||||||
|
void sasLoop(void *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Convert the process state enum to a string (useful for logging).
|
* @brief Convert the process state enum to a string (useful for logging).
|
||||||
*/
|
*/
|
||||||
|
|
@ -146,11 +148,12 @@ private:
|
||||||
static void shutdownExistingProcesses();
|
static void shutdownExistingProcesses();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Thread *m_thread = nullptr;
|
|
||||||
bool m_running = true;
|
bool m_running = true;
|
||||||
|
std::unique_ptr<Thread> m_mainThread;
|
||||||
|
std::unique_ptr<Thread> m_outputThread;
|
||||||
|
std::unique_ptr<Thread> m_sasThread;
|
||||||
HANDLE m_outputWritePipe = nullptr;
|
HANDLE m_outputWritePipe = nullptr;
|
||||||
HANDLE m_outputReadPipe = nullptr;
|
HANDLE m_outputReadPipe = nullptr;
|
||||||
Thread *m_outputThread = nullptr;
|
|
||||||
bool m_elevateProcess = false;
|
bool m_elevateProcess = false;
|
||||||
MSWindowsSession m_session;
|
MSWindowsSession m_session;
|
||||||
int m_startFailures = 0;
|
int m_startFailures = 0;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue