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:
Nick Bolton
2025-03-10 14:13:43 +00:00
committed by Chris Rizzitello
parent 38f8159e9d
commit a099276e4e
4 changed files with 127 additions and 51 deletions

View 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;
};

View File

@ -1,19 +1,17 @@
/*
* 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-License-Identifier: GPL-2.0-only WITH LicenseRef-OpenSSL-Exception
*/
#include "platform/MSWindowsKeyState.h"
#include "arch/win32/ArchMiscWindows.h"
#include "base/FunctionJob.h"
#include "arch/win32/XArchWindows.h"
#include "base/IEventQueue.h"
#include "base/Log.h"
#include "base/TMethodEventJob.h"
#include "mt/Thread.h"
#include "platform/MSWindowsDesks.h"
#include "platform/MSWindowsHandle.h"
// extended mouse buttons
#if !defined(VK_XBUTTON1)
@ -750,26 +748,39 @@ bool MSWindowsKeyState::fakeKeyRepeat(
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()
{
// We must use SendSAS (Secure Attention Sequence) to simulate Ctrl+Alt+Del as of Windows Vista.
// 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
//
// 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.
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");
// The daemon creates this event with default permissions, which means this process must be
// elevated to be able to open it. If not elevated, an access denied error will be returned.
MSWindowsHandle sendSasEvent(OpenEvent(EVENT_MODIFY_STATE, FALSE, kSendSasEventName));
if (!sendSasEvent.get()) {
XArchEvalWindows error;
LOG_ERR("couldn't open SAS event, unable to simulate ctrl+alt+del, error: %s", error.eval().c_str());
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

View File

@ -14,6 +14,7 @@
#include "base/log_outputters.h"
#include "deskflow/App.h"
#include "mt/Thread.h"
#include "platform/MSWindowsHandle.h"
#include <Shellapi.h>
#include <UserEnv.h>
@ -86,24 +87,28 @@ MSWindowsWatchdog::MSWindowsWatchdog(bool foreground) : m_foreground(foreground)
void MSWindowsWatchdog::startAsync()
{
m_thread = new Thread(new TMethodJob<MSWindowsWatchdog>(this, &MSWindowsWatchdog::mainLoop, nullptr));
m_outputThread = new Thread(new TMethodJob<MSWindowsWatchdog>(this, &MSWindowsWatchdog::outputLoop, 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_sasThread = std::make_unique<Thread>(new TMethodJob(this, &MSWindowsWatchdog::sasLoop, nullptr));
}
void MSWindowsWatchdog::stop()
{
const auto kThreadWaitSeconds = 5;
m_running = false;
if (!m_thread->wait(5)) {
if (!m_mainThread->wait(kThreadWaitSeconds)) {
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"));
}
delete m_outputThread;
if (!m_sasThread->wait(kThreadWaitSeconds)) {
LOG((CLOG_WARN "could not stop sas thread"));
}
}
HANDLE
@ -232,10 +237,6 @@ void MSWindowsWatchdog::mainLoop(void *)
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.
ARCH->sleep(0.1);
}
@ -561,23 +562,36 @@ void MSWindowsWatchdog::initSasFunc()
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) {
throw XArch("SendSAS function not initialized");
}
HANDLE sendSasEvent = CreateEvent(nullptr, FALSE, FALSE, kSendSasEventName);
if (sendSasEvent == nullptr) {
LOG_ERR("could not create SendSAS event");
throw XArch(new XArchEvalWindows());
}
while (m_running) {
if (m_processState != ProcessState::Running) {
LOG_DEBUG2("watchdog: not running, skipping SendSAS");
ARCH->sleep(1);
continue;
}
// use SendSAS event to wait for next session (timeout 1 second).
if (WaitForSingleObject(sendSasEvent, 1000) == WAIT_OBJECT_0) {
LOG_DEBUG("calling SendSAS from sas.dll");
m_sendSasFunc(FALSE);
}
// Create a an event so that other processes can tell the daemon to call the `SendSAS` function.
MSWindowsHandle sendSasEvent(CreateEvent(nullptr, FALSE, FALSE, kSendSasEventName));
if (sendSasEvent.get() == nullptr) {
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);
}
}
}

View File

@ -120,11 +120,6 @@ private:
*/
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.
*
@ -135,6 +130,13 @@ private:
*/
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).
*/
@ -146,11 +148,12 @@ private:
static void shutdownExistingProcesses();
private:
Thread *m_thread = nullptr;
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_outputReadPipe = nullptr;
Thread *m_outputThread = nullptr;
bool m_elevateProcess = false;
MSWindowsSession m_session;
int m_startFailures = 0;