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
#include <string>
#include <QCoreApplication>
#include <QSettings>
using namespace deskflow::core;
@ -45,26 +46,40 @@ void DaemonApp::saveLogLevel(const QString &logLevel) const
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");
m_elevate = elevate;
Settings::setValue(Settings::Daemon::Elevate, m_elevate);
}
void DaemonApp::setCommand(const QString &command)
{
LOG_DEBUG("service command updated");
Settings::setValue(Settings::Daemon::Command, command);
m_command = command.toStdString();
LOG_DEBUG("config file updated: %s", configFile.toUtf8().constData());
m_configFile = configFile;
Settings::setValue(Settings::Daemon::ConfigFile, configFile);
}
void DaemonApp::applyWatchdogCommand() const
{
LOG_DEBUG("applying watchdog command");
#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
LOG_ERR("applying watchdog command not implemented on this platform");
#endif
@ -74,8 +89,9 @@ void DaemonApp::clearWatchdogCommand()
{
LOG_DEBUG("clearing watchdog command");
// Clear the setting to prevent it from being next time the daemon starts.
setCommand("");
// Clear the persisted config path so the daemon does not auto-start the core on next boot.
m_configFile.clear();
Settings::setValue(Settings::Daemon::ConfigFile);
#if defined(Q_OS_WIN)
m_pWatchdog->setProcessConfig("", false);
@ -84,11 +100,11 @@ void DaemonApp::clearWatchdogCommand()
#endif
}
void DaemonApp::clearSettings() const
void DaemonApp::clearSettings()
{
LOG_INFO("clearing daemon settings");
Settings::setValue(Settings::Daemon::Command);
Settings::setValue(Settings::Daemon::Elevate);
m_configFile.clear();
Settings::setValue(Settings::Daemon::ConfigFile);
Settings::setValue(Settings::Daemon::LogFile);
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,
// 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::elevateModeChanged, this, &DaemonApp::setElevate, Qt::DirectConnection);
connect(ipcServer, &ipc::DaemonIpcServer::commandChanged, this, &DaemonApp::setCommand, Qt::DirectConnection);
connect(ipcServer, &ipc::DaemonIpcServer::configFileChanged, this, &DaemonApp::setConfigFile, Qt::DirectConnection);
connect(
ipcServer, &ipc::DaemonIpcServer::startProcessRequested, this, &DaemonApp::applyWatchdogCommand,
Qt::DirectConnection
@ -139,11 +154,11 @@ void DaemonApp::run(QThread &daemonThread)
#if defined(Q_OS_WIN)
m_pWatchdog = std::make_unique<MSWindowsWatchdog>(m_foreground, *m_pFileLogOutputter);
auto command = Settings::value(Settings::Daemon::Command).toString().toStdString();
bool elevate = Settings::value(Settings::Daemon::Elevate).toBool();
if (!command.empty()) {
LOG_DEBUG("using last known command: %s", command.c_str());
m_pWatchdog->setProcessConfig(command, elevate);
if (const auto persistedConfig = Settings::value(Settings::Daemon::ConfigFile).toString();
!persistedConfig.isEmpty()) {
LOG_DEBUG("using last known config file: %s", persistedConfig.toUtf8().constData());
m_configFile = persistedConfig;
applyWatchdogCommand();
}
#endif

View file

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

View file

@ -66,7 +66,7 @@ public:
};
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 LogFile = QStringLiteral("daemon/logFile");
inline static const auto LogLevel = QStringLiteral("daemon/logLevel");
@ -225,7 +225,7 @@ private:
, Settings::Core::UseHooks
, Settings::Core::UseWlClipboard
, Settings::Core::Language
, Settings::Daemon::Command
, Settings::Daemon::ConfigFile
, Settings::Daemon::Elevate
, Settings::Daemon::LogFile
, Settings::Daemon::LogLevel

View file

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

View file

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

View file

@ -27,8 +27,7 @@ public:
Q_SIGNALS:
void logLevelChanged(const QString &logLevel);
void elevateModeChanged(bool elevate);
void commandChanged(const QString &command);
void configFileChanged(const QString &configFile);
void startProcessRequested();
void stopProcessRequested();
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) {
qFatal("core process must be in starting state");
}
QString commandQuoted = makeQuotedArgs(m_appPath, args);
qInfo("running command: %s", qPrintable(commandQuoted));
const auto configFile = Settings::settingsFile();
qInfo("sending start to daemon (config file: %s)", qPrintable(configFile));
auto sendStart = [this, commandQuoted] {
m_daemonIpcClient->sendStartProcess(commandQuoted, Settings::value(Settings::Daemon::Elevate).toBool());
auto sendStart = [this, configFile] {
m_daemonIpcClient->sendConfigFile(configFile);
m_daemonIpcClient->sendStartProcess();
setProcessState(ProcessState::Started);
};
@ -412,8 +413,7 @@ void CoreProcess::start(std::optional<ProcessMode> processModeOption)
if (processMode == ProcessMode::Desktop) {
startForegroundProcess(args);
} else if (processMode == ProcessMode::Service) {
args.append({QStringLiteral("--settings"), Settings::settingsFile()});
startProcessFromDaemon(args);
startProcessFromDaemon();
}
m_lastProcessMode = processMode;

View file

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

View file

@ -21,11 +21,13 @@ void DaemonIpcClient::sendLogLevel(const QString &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"));
}

View file

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