fix(cve): run switch commands as normal user on Windows to prevent privilege escalation

CVE-2026-41477
This commit is contained in:
Nick Bolton 2026-04-23 17:47:45 +01:00 committed by Chris Rizzitello
parent e7040a1f82
commit 5c480ca515
6 changed files with 89 additions and 9 deletions

View file

@ -73,6 +73,7 @@ int main(int argc, char **argv)
// It's important to write the version number to the log file so we can be certain the old daemon
// was uninstalled, since sometimes Windows services can get stuck and fail to be removed.
LOG_PRINT("%s v%s", qPrintable(QCoreApplication::applicationName()), kDisplayVersion);
LOG_INFO("settings file: %s", qPrintable(Settings::settingsFile()));
// Default log level to system setting (found in Registry).
auto logLevel = Settings::value(Settings::Daemon::LogLevel).toString().toStdString();

View file

@ -91,6 +91,10 @@ add_library(${lib_name} STATIC ${PLATFORM_CODE}
target_link_libraries(${lib_name} PUBLIC common Qt6::Core Qt6::Network)
if(WIN32)
target_link_libraries(${lib_name} PRIVATE platform)
endif()
if(UNIX)
target_link_libraries(
${lib_name}

View file

@ -12,8 +12,35 @@
#include <QProcess>
#ifdef Q_OS_WIN
#include "arch/win32/ArchMiscWindows.h"
#include "platform/MSWindowsProcess.h"
#endif
namespace deskflow {
namespace {
bool runScreenCommand(const QString &commandLine)
{
#ifdef Q_OS_WIN
using deskflow::platform::MSWindowsProcess;
if (ArchMiscWindows::isProcessElevated()) {
LOG_DEBUG("current process is elevated, starting detached process as session user");
return MSWindowsProcess::startDetachedAsSessionUser(commandLine.toStdWString());
}
#endif
auto args = QProcess::splitCommand(commandLine);
if (args.isEmpty()) {
return false;
}
const auto program = args.takeFirst();
return QProcess::startDetached(program, args);
}
} // namespace
//
// Screen
//
@ -123,10 +150,9 @@ void Screen::enter(KeyModifierMask toggleMask)
}
if (Settings::value(Settings::Core::EnableEnterCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenEnterCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen enter command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
const auto commandLine = Settings::value(Settings::Core::ScreenEnterCommand).toString();
LOG_DEBUG("running screen enter command: %s", qPrintable(commandLine));
if (!runScreenCommand(commandLine))
LOG_ERR("failed to run screen enter command");
}
}
@ -151,10 +177,9 @@ bool Screen::leave()
m_screen->leave();
if (Settings::value(Settings::Core::EnableExitCommand).toBool()) {
auto args = QProcess::splitCommand(Settings::value(Settings::Core::ScreenExitCommand).toString());
const auto command = args.takeFirst();
LOG_DEBUG("running screen exit command: %s %s", qPrintable(command), qPrintable(args.join(" ")));
if (!QProcess::startDetached(command, args))
const auto commandLine = Settings::value(Settings::Core::ScreenExitCommand).toString();
LOG_DEBUG("running screen exit command: %s", qPrintable(commandLine));
if (!runScreenCommand(commandLine))
LOG_ERR("failed to run screen exit command");
}

View file

@ -11,6 +11,7 @@
#include "base/Log.h"
#include "common/Constants.h"
#include "common/ExitCodes.h"
#include "platform/MSWindowsSession.h"
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
@ -188,6 +189,51 @@ void MSWindowsProcess::shutdown(HANDLE handle, DWORD pid, int timeout)
}
}
bool MSWindowsProcess::startDetachedAsSessionUser(const std::wstring &command)
{
MSWindowsSession session;
session.updateActiveSession();
HANDLE userToken = nullptr;
try {
userToken = session.getUserToken(nullptr);
} catch (const std::runtime_error &e) {
LOG_ERR("could not get session user token: %s", e.what());
return false;
}
LPVOID environment = nullptr;
if (!CreateEnvironmentBlock(&environment, userToken, FALSE)) {
LOG_ERR("could not create environment block, error: %s", windowsErrorToString(GetLastError()).c_str());
CloseHandle(userToken);
return false;
}
STARTUPINFOW si = {};
si.cb = sizeof(si);
PROCESS_INFORMATION pi = {};
std::wstring mutableCommand = command;
const DWORD flags = CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE;
LOG_DEBUG("starting detached process as session user, command: %s", command.c_str());
const BOOL ok = CreateProcessAsUserW(
userToken, nullptr, mutableCommand.data(), nullptr, nullptr, FALSE, flags, environment, nullptr, &si, &pi
);
DestroyEnvironmentBlock(environment);
CloseHandle(userToken);
if (!ok) {
LOG_ERR("could not start process as session user, error: %s", windowsErrorToString(GetLastError()).c_str());
return false;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
void MSWindowsProcess::createPipes()
{
SECURITY_ATTRIBUTES saAttr;

View file

@ -38,6 +38,10 @@ public:
static void shutdown(HANDLE handle, DWORD pid, int timeout = kDefaultShutdownTimeout);
/// Launch @p command as a detached process under the active console user's token, so the
/// child does not inherit the caller's (SYSTEM) privileges.
static bool startDetachedAsSessionUser(const std::wstring &command);
private:
void setStartupInfo(STARTUPINFO &si);

View file

@ -2,7 +2,7 @@
# SPDX-License-Identifier: MIT
if(WIN32)
set(extra_libs version)
set(extra_libs platform version)
endif()
create_test(