From a099276e4ea29a83d02cf7f45301effdba5256c7 Mon Sep 17 00:00:00 2001 From: Nick Bolton Date: Mon, 10 Mar 2025 14:13:43 +0000 Subject: [PATCH] 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). --- src/lib/platform/MSWindowsHandle.h | 48 +++++++++++++++++++++ src/lib/platform/MSWindowsKeyState.cpp | 53 ++++++++++++++--------- src/lib/platform/MSWindowsWatchdog.cpp | 60 ++++++++++++++++---------- src/lib/platform/MSWindowsWatchdog.h | 17 +++++--- 4 files changed, 127 insertions(+), 51 deletions(-) create mode 100644 src/lib/platform/MSWindowsHandle.h diff --git a/src/lib/platform/MSWindowsHandle.h b/src/lib/platform/MSWindowsHandle.h new file mode 100644 index 000000000..ab95a566a --- /dev/null +++ b/src/lib/platform/MSWindowsHandle.h @@ -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 + +/** + * @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; +}; diff --git a/src/lib/platform/MSWindowsKeyState.cpp b/src/lib/platform/MSWindowsKeyState.cpp index c4e55ee46..fe8e0423a 100644 --- a/src/lib/platform/MSWindowsKeyState.cpp +++ b/src/lib/platform/MSWindowsKeyState.cpp @@ -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 diff --git a/src/lib/platform/MSWindowsWatchdog.cpp b/src/lib/platform/MSWindowsWatchdog.cpp index 52c989bb3..fb811e526 100644 --- a/src/lib/platform/MSWindowsWatchdog.cpp +++ b/src/lib/platform/MSWindowsWatchdog.cpp @@ -14,6 +14,7 @@ #include "base/log_outputters.h" #include "deskflow/App.h" #include "mt/Thread.h" +#include "platform/MSWindowsHandle.h" #include #include @@ -86,24 +87,28 @@ MSWindowsWatchdog::MSWindowsWatchdog(bool foreground) : m_foreground(foreground) void MSWindowsWatchdog::startAsync() { - m_thread = new Thread(new TMethodJob(this, &MSWindowsWatchdog::mainLoop, nullptr)); - - m_outputThread = new Thread(new TMethodJob(this, &MSWindowsWatchdog::outputLoop, nullptr)); + m_mainThread = std::make_unique(new TMethodJob(this, &MSWindowsWatchdog::mainLoop, nullptr)); + m_outputThread = std::make_unique(new TMethodJob(this, &MSWindowsWatchdog::outputLoop, nullptr)); + m_sasThread = std::make_unique(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); + } + } } diff --git a/src/lib/platform/MSWindowsWatchdog.h b/src/lib/platform/MSWindowsWatchdog.h index 60cca8067..a9464224c 100644 --- a/src/lib/platform/MSWindowsWatchdog.h +++ b/src/lib/platform/MSWindowsWatchdog.h @@ -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 m_mainThread; + std::unique_ptr m_outputThread; + std::unique_ptr 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;