fix(cve): remove ipc command that creates privilege escalation vulnerability

CVE-2026-41477
This commit is contained in:
Nick Bolton 2026-04-13 15:57:00 +01:00 committed by Chris Rizzitello
parent f0631f7a3c
commit e7040a1f82
10 changed files with 74 additions and 84 deletions

View file

@ -27,7 +27,8 @@
#endif #endif
#include <string> #include <QCoreApplication>
#include <QSettings>
using namespace deskflow::core; using namespace deskflow::core;
@ -45,26 +46,40 @@ void DaemonApp::saveLogLevel(const QString &logLevel) const
Settings::setValue(Settings::Daemon::LogLevel, logLevel); Settings::setValue(Settings::Daemon::LogLevel, logLevel);
} }
void DaemonApp::setElevate(bool elevate) void DaemonApp::setConfigFile(const QString &configFile)
{ {
LOG_DEBUG("elevate value changed: %s", elevate ? "yes" : "no"); LOG_DEBUG("config file updated: %s", configFile.toUtf8().constData());
m_elevate = elevate; m_configFile = configFile;
Settings::setValue(Settings::Daemon::Elevate, m_elevate); Settings::setValue(Settings::Daemon::ConfigFile, configFile);
}
void DaemonApp::setCommand(const QString &command)
{
LOG_DEBUG("service command updated");
Settings::setValue(Settings::Daemon::Command, command);
m_command = command.toStdString();
} }
void DaemonApp::applyWatchdogCommand() const void DaemonApp::applyWatchdogCommand() const
{ {
LOG_DEBUG("applying watchdog command");
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
m_pWatchdog->setProcessConfig(m_command, m_elevate); if (m_configFile.isEmpty()) {
LOG_ERR("cannot apply watchdog command: no config file set");
return;
}
QSettings config(m_configFile, QSettings::IniFormat);
const auto coreMode = config.value(Settings::Core::CoreMode).toInt();
const auto elevate = config.value(Settings::Daemon::Elevate, !Settings::isPortableMode()).toBool();
QString modeArg;
if (coreMode == Settings::CoreMode::Server) {
modeArg = QStringLiteral("server");
} else if (coreMode == Settings::CoreMode::Client) {
modeArg = QStringLiteral("client");
} else {
LOG_ERR("cannot apply watchdog command: invalid core mode in config: %d", coreMode);
return;
}
const auto corePath = QStringLiteral("%1/%2").arg(QCoreApplication::applicationDirPath(), kCoreBinName);
const auto command = QStringLiteral("\"%1\" %2 --settings \"%3\"").arg(corePath, modeArg, m_configFile).toStdString();
LOG_DEBUG("applying watchdog command (elevate: %s)", elevate ? "yes" : "no");
m_pWatchdog->setProcessConfig(command, elevate);
#else #else
LOG_ERR("applying watchdog command not implemented on this platform"); LOG_ERR("applying watchdog command not implemented on this platform");
#endif #endif
@ -74,8 +89,9 @@ void DaemonApp::clearWatchdogCommand()
{ {
LOG_DEBUG("clearing watchdog command"); LOG_DEBUG("clearing watchdog command");
// Clear the setting to prevent it from being next time the daemon starts. // Clear the persisted config path so the daemon does not auto-start the core on next boot.
setCommand(""); m_configFile.clear();
Settings::setValue(Settings::Daemon::ConfigFile);
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
m_pWatchdog->setProcessConfig("", false); m_pWatchdog->setProcessConfig("", false);
@ -84,11 +100,11 @@ void DaemonApp::clearWatchdogCommand()
#endif #endif
} }
void DaemonApp::clearSettings() const void DaemonApp::clearSettings()
{ {
LOG_INFO("clearing daemon settings"); LOG_INFO("clearing daemon settings");
Settings::setValue(Settings::Daemon::Command); m_configFile.clear();
Settings::setValue(Settings::Daemon::Elevate); Settings::setValue(Settings::Daemon::ConfigFile);
Settings::setValue(Settings::Daemon::LogFile); Settings::setValue(Settings::Daemon::LogFile);
Settings::setValue(Settings::Daemon::LogLevel); Settings::setValue(Settings::Daemon::LogLevel);
} }
@ -98,8 +114,7 @@ void DaemonApp::connectIpcServer(const ipc::DaemonIpcServer *ipcServer) const
// Use direct connection as this object is on it's own thread, // Use direct connection as this object is on it's own thread,
// and so is on a different event loop to the main Qt loop. // and so is on a different event loop to the main Qt loop.
connect(ipcServer, &ipc::DaemonIpcServer::logLevelChanged, this, &DaemonApp::saveLogLevel, Qt::DirectConnection); connect(ipcServer, &ipc::DaemonIpcServer::logLevelChanged, this, &DaemonApp::saveLogLevel, Qt::DirectConnection);
connect(ipcServer, &ipc::DaemonIpcServer::elevateModeChanged, this, &DaemonApp::setElevate, Qt::DirectConnection); connect(ipcServer, &ipc::DaemonIpcServer::configFileChanged, this, &DaemonApp::setConfigFile, Qt::DirectConnection);
connect(ipcServer, &ipc::DaemonIpcServer::commandChanged, this, &DaemonApp::setCommand, Qt::DirectConnection);
connect( connect(
ipcServer, &ipc::DaemonIpcServer::startProcessRequested, this, &DaemonApp::applyWatchdogCommand, ipcServer, &ipc::DaemonIpcServer::startProcessRequested, this, &DaemonApp::applyWatchdogCommand,
Qt::DirectConnection Qt::DirectConnection
@ -139,11 +154,11 @@ void DaemonApp::run(QThread &daemonThread)
#if defined(Q_OS_WIN) #if defined(Q_OS_WIN)
m_pWatchdog = std::make_unique<MSWindowsWatchdog>(m_foreground, *m_pFileLogOutputter); m_pWatchdog = std::make_unique<MSWindowsWatchdog>(m_foreground, *m_pFileLogOutputter);
auto command = Settings::value(Settings::Daemon::Command).toString().toStdString(); if (const auto persistedConfig = Settings::value(Settings::Daemon::ConfigFile).toString();
bool elevate = Settings::value(Settings::Daemon::Elevate).toBool(); !persistedConfig.isEmpty()) {
if (!command.empty()) { LOG_DEBUG("using last known config file: %s", persistedConfig.toUtf8().constData());
LOG_DEBUG("using last known command: %s", command.c_str()); m_configFile = persistedConfig;
m_pWatchdog->setProcessConfig(command, elevate); applyWatchdogCommand();
} }
#endif #endif

View file

@ -6,8 +6,6 @@
#pragma once #pragma once
#include <string>
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
@ -48,11 +46,10 @@ private:
int mainLoop(); int mainLoop();
int daemonLoop(); int daemonLoop();
void saveLogLevel(const QString &logLevel) const; void saveLogLevel(const QString &logLevel) const;
void setElevate(bool elevate); void setConfigFile(const QString &configFile);
void setCommand(const QString &command);
void applyWatchdogCommand() const; void applyWatchdogCommand() const;
void clearWatchdogCommand(); void clearWatchdogCommand();
void clearSettings() const; void clearSettings();
static void showConsole(); static void showConsole();
@ -63,7 +60,6 @@ private:
IEventQueue &m_events; IEventQueue &m_events;
FileLogOutputter *m_pFileLogOutputter = nullptr; FileLogOutputter *m_pFileLogOutputter = nullptr;
deskflow::core::ipc::DaemonIpcServer *m_ipcServer = nullptr; deskflow::core::ipc::DaemonIpcServer *m_ipcServer = nullptr;
std::string m_command = ""; QString m_configFile;
bool m_elevate = false;
bool m_foreground = false; bool m_foreground = false;
}; };

View file

@ -66,7 +66,7 @@ public:
}; };
struct Daemon struct Daemon
{ {
inline static const auto Command = QStringLiteral("daemon/command"); inline static const auto ConfigFile = QStringLiteral("daemon/configFile");
inline static const auto Elevate = QStringLiteral("daemon/elevate"); inline static const auto Elevate = QStringLiteral("daemon/elevate");
inline static const auto LogFile = QStringLiteral("daemon/logFile"); inline static const auto LogFile = QStringLiteral("daemon/logFile");
inline static const auto LogLevel = QStringLiteral("daemon/logLevel"); inline static const auto LogLevel = QStringLiteral("daemon/logLevel");
@ -225,7 +225,7 @@ private:
, Settings::Core::UseHooks , Settings::Core::UseHooks
, Settings::Core::UseWlClipboard , Settings::Core::UseWlClipboard
, Settings::Core::Language , Settings::Core::Language
, Settings::Daemon::Command , Settings::Daemon::ConfigFile
, Settings::Daemon::Elevate , Settings::Daemon::Elevate
, Settings::Daemon::LogFile , Settings::Daemon::LogFile
, Settings::Daemon::LogLevel , Settings::Daemon::LogLevel

View file

@ -27,10 +27,8 @@ void DaemonIpcServer::processCommand(QLocalSocket *clientSocket, const QString &
{ {
if (command == QStringLiteral("logLevel")) { if (command == QStringLiteral("logLevel")) {
processLogLevel(clientSocket, parts); processLogLevel(clientSocket, parts);
} else if (command == QStringLiteral("elevate")) { } else if (command == QStringLiteral("configFile")) {
processElevate(clientSocket, parts); processConfigFile(clientSocket, parts);
} else if (command == QStringLiteral("command")) {
processCommandMessage(clientSocket, parts);
} else if (command == QStringLiteral("start")) { } else if (command == QStringLiteral("start")) {
LOG_DEBUG("daemon ipc server got start message"); LOG_DEBUG("daemon ipc server got start message");
Q_EMIT startProcessRequested(); Q_EMIT startProcessRequested();
@ -71,43 +69,23 @@ void DaemonIpcServer::processLogLevel(QLocalSocket *&clientSocket, const QString
writeToClientSocket(clientSocket, kAckMessage); writeToClientSocket(clientSocket, kAckMessage);
} }
void DaemonIpcServer::processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts) void DaemonIpcServer::processConfigFile(QLocalSocket *&clientSocket, const QStringList &messageParts)
{ {
if (messageParts.size() < 2) { if (messageParts.size() < 2) {
LOG_ERR("daemon ipc server got invalid elevate message"); LOG_ERR("daemon ipc server got invalid config file message");
writeToClientSocket(clientSocket, kErrorMessage); writeToClientSocket(clientSocket, kErrorMessage);
return; return;
} }
const auto &elevate = messageParts.at(1); const auto &configFile = messageParts.at(1);
if (elevate != QStringLiteral("yes") && elevate != QStringLiteral("no")) { if (configFile.isEmpty()) {
LOG_ERR("daemon ipc server got invalid elevate value: %s", elevate.toUtf8().constData()); LOG_ERR("daemon ipc server got empty config file path");
writeToClientSocket(clientSocket, kErrorMessage); writeToClientSocket(clientSocket, kErrorMessage);
return; return;
} }
LOG_DEBUG("daemon ipc server got new elevate value: %s", elevate.toUtf8().constData()); LOG_DEBUG("daemon ipc server got config file: %s", configFile.toUtf8().constData());
Q_EMIT elevateModeChanged(elevate == QStringLiteral("yes")); Q_EMIT configFileChanged(configFile);
writeToClientSocket(clientSocket, kAckMessage);
}
void DaemonIpcServer::processCommandMessage(QLocalSocket *&clientSocket, const QStringList &messageParts)
{
if (messageParts.size() < 2) {
LOG_ERR("daemon ipc server got invalid command message");
writeToClientSocket(clientSocket, kErrorMessage);
return;
}
const auto &command = messageParts.at(1);
if (command.isEmpty()) {
LOG_ERR("daemon ipc server got empty command");
writeToClientSocket(clientSocket, kErrorMessage);
return;
}
LOG_DEBUG("daemon ipc server got new command: %s", command.toUtf8().constData());
Q_EMIT commandChanged(command);
writeToClientSocket(clientSocket, kAckMessage); writeToClientSocket(clientSocket, kAckMessage);
} }

View file

@ -25,8 +25,7 @@ public:
private: private:
void processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) override; void processCommand(QLocalSocket *clientSocket, const QString &command, const QStringList &parts) override;
void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts); void processLogLevel(QLocalSocket *&clientSocket, const QStringList &messageParts);
void processElevate(QLocalSocket *&clientSocket, const QStringList &messageParts); void processConfigFile(QLocalSocket *&clientSocket, const QStringList &messageParts);
void processCommandMessage(QLocalSocket *&clientSocket, const QStringList &messageParts);
private: private:
const QString m_logFilename; const QString m_logFilename;

View file

@ -27,8 +27,7 @@ public:
Q_SIGNALS: Q_SIGNALS:
void logLevelChanged(const QString &logLevel); void logLevelChanged(const QString &logLevel);
void elevateModeChanged(bool elevate); void configFileChanged(const QString &configFile);
void commandChanged(const QString &command);
void startProcessRequested(); void startProcessRequested();
void stopProcessRequested(); void stopProcessRequested();
void clearSettingsRequested(); void clearSettingsRequested();

View file

@ -214,17 +214,18 @@ void CoreProcess::startForegroundProcess(const QStringList &args)
} }
} }
void CoreProcess::startProcessFromDaemon(const QStringList &args) void CoreProcess::startProcessFromDaemon()
{ {
if (m_processState != ProcessState::Starting) { if (m_processState != ProcessState::Starting) {
qFatal("core process must be in starting state"); qFatal("core process must be in starting state");
} }
QString commandQuoted = makeQuotedArgs(m_appPath, args); const auto configFile = Settings::settingsFile();
qInfo("running command: %s", qPrintable(commandQuoted)); qInfo("sending start to daemon (config file: %s)", qPrintable(configFile));
auto sendStart = [this, commandQuoted] { auto sendStart = [this, configFile] {
m_daemonIpcClient->sendStartProcess(commandQuoted, Settings::value(Settings::Daemon::Elevate).toBool()); m_daemonIpcClient->sendConfigFile(configFile);
m_daemonIpcClient->sendStartProcess();
setProcessState(ProcessState::Started); setProcessState(ProcessState::Started);
}; };
@ -412,8 +413,7 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption)
if (processMode == ProcessMode::Desktop) { if (processMode == ProcessMode::Desktop) {
startForegroundProcess(args); startForegroundProcess(args);
} else if (processMode == ProcessMode::Service) { } else if (processMode == ProcessMode::Service) {
args.append({QStringLiteral("--settings"), Settings::settingsFile()}); startProcessFromDaemon();
startProcessFromDaemon(args);
} }
m_lastProcessMode = processMode; m_lastProcessMode = processMode;

View file

@ -104,7 +104,7 @@ private Q_SLOTS:
private: private:
void startForegroundProcess(const QStringList &args); void startForegroundProcess(const QStringList &args);
void startProcessFromDaemon(const QStringList &args); void startProcessFromDaemon();
void stopForegroundProcess() const; void stopForegroundProcess() const;
void stopProcessFromDaemon(); void stopProcessFromDaemon();
QPair<bool, QString> persistServerConfig() const; QPair<bool, QString> persistServerConfig() const;

View file

@ -21,11 +21,13 @@ void DaemonIpcClient::sendLogLevel(const QString &logLevel)
sendMessage(QStringLiteral("logLevel=%1").arg(logLevel)); sendMessage(QStringLiteral("logLevel=%1").arg(logLevel));
} }
void DaemonIpcClient::sendStartProcess(const QString &command, bool elevate) void DaemonIpcClient::sendConfigFile(const QString &configFile)
{
sendMessage(QStringLiteral("configFile=%1").arg(configFile));
}
void DaemonIpcClient::sendStartProcess()
{ {
const auto elevateStr = elevate ? QStringLiteral("yes") : QStringLiteral("no");
sendMessage(QStringLiteral("elevate=%1").arg(elevateStr));
sendMessage(QStringLiteral("command=%1").arg(command));
sendMessage(QStringLiteral("start")); sendMessage(QStringLiteral("start"));
} }

View file

@ -19,7 +19,8 @@ class DaemonIpcClient : public IpcClient
public: public:
explicit DaemonIpcClient(QObject *parent = nullptr); explicit DaemonIpcClient(QObject *parent = nullptr);
void sendLogLevel(const QString &logLevel); void sendLogLevel(const QString &logLevel);
void sendStartProcess(const QString &command, bool elevate); void sendConfigFile(const QString &configFile);
void sendStartProcess();
void sendStopProcess(); void sendStopProcess();
void sendClearSettings(); void sendClearSettings();
void requestLogPath(); void requestLogPath();