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:
committed by
Chris Rizzitello
parent
38f8159e9d
commit
a099276e4e
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
|
||||
* 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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user