|
| 1 | +// SPDX-FileCopyrightText: 2024 - 2026 UnionTech Software Technology Co., Ltd. |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | + |
| 5 | +#include "securityloaderhelper.h" |
| 6 | +#include "dbusconstant.h" |
| 7 | + |
| 8 | +#include <QFile> |
| 9 | +#include <QJsonDocument> |
| 10 | +#include <QJsonObject> |
| 11 | +#include <QJsonParseError> |
| 12 | +#include <QLoggingCategory> |
| 13 | +#include <QDBusConnection> |
| 14 | +#include <QDBusConnectionInterface> |
| 15 | +#include <QDBusInterface> |
| 16 | +#include <QDBusReply> |
| 17 | +#include <unistd.h> |
| 18 | + |
| 19 | +Q_LOGGING_CATEGORY(secLoader, "org.deepin.dde.lock.securityloader") |
| 20 | + |
| 21 | +const QString SecurityLoaderHelper::DEFAULT_CONFIG_PATH = ":/files/permission-interfaces/auth.json"; |
| 22 | + |
| 23 | +SecurityLoaderHelper::SecurityLoaderHelper(QObject *parent) |
| 24 | + : QObject(parent) |
| 25 | +{ |
| 26 | +} |
| 27 | + |
| 28 | +SecurityLoaderHelper::~SecurityLoaderHelper() {} |
| 29 | + |
| 30 | +SecurityLoaderHelper &SecurityLoaderHelper::instance() |
| 31 | +{ |
| 32 | + static SecurityLoaderHelper instance; |
| 33 | + return instance; |
| 34 | +} |
| 35 | + |
| 36 | +void SecurityLoaderHelper::doSecurityLoader(int fd1, int fd2) |
| 37 | +{ |
| 38 | + if (fd1 < 0 || fd2 < 0) { |
| 39 | + qCWarning(secLoader) << "Not loaded by loader, skipping handshake"; |
| 40 | + return; |
| 41 | + } |
| 42 | + |
| 43 | + qCInfo(secLoader) << "Detected loader injection: fd1=" << fd1 << "fd2=" << fd2; |
| 44 | + |
| 45 | + loadConfig(); |
| 46 | + // appendCurrentUserAccountsUserDest(); |
| 47 | + if (!performHandshake(fd1, fd2)) { |
| 48 | + qCWarning(secLoader) << "Security loader handshake failed"; |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +void SecurityLoaderHelper::loadConfig(const QString &configPath) |
| 53 | +{ |
| 54 | + QString path = configPath.isEmpty() ? DEFAULT_CONFIG_PATH : configPath; |
| 55 | + |
| 56 | + qCInfo(secLoader) << "Loading permission config from:" << path; |
| 57 | + |
| 58 | + m_destList = QJsonArray(); |
| 59 | + parseJsonFile(path); |
| 60 | + |
| 61 | + qCInfo(secLoader) << "Loaded" << m_destList.size() << "D-Bus interfaces to authorize"; |
| 62 | +} |
| 63 | + |
| 64 | +void SecurityLoaderHelper::parseJsonFile(const QString &filePath) |
| 65 | +{ |
| 66 | + QFile file(filePath); |
| 67 | + if (!file.open(QIODevice::ReadOnly)) { |
| 68 | + qCWarning(secLoader) << "Cannot open file:" << filePath; |
| 69 | + return; |
| 70 | + } |
| 71 | + |
| 72 | + QJsonParseError error; |
| 73 | + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); |
| 74 | + file.close(); |
| 75 | + |
| 76 | + if (error.error != QJsonParseError::NoError) { |
| 77 | + qCWarning(secLoader) << "JSON parse error in" << filePath << ":" << error.errorString(); |
| 78 | + return; |
| 79 | + } |
| 80 | + |
| 81 | + QJsonObject root = doc.object(); |
| 82 | + if (!root.contains("DestList") || !root["DestList"].isArray()) { |
| 83 | + qCWarning(secLoader) << "Invalid config format in" << filePath << ": missing DestList"; |
| 84 | + return; |
| 85 | + } |
| 86 | + |
| 87 | + for (const auto &item : root["DestList"].toArray()) { |
| 88 | + appendDest(item.toObject()); |
| 89 | + } |
| 90 | +} |
| 91 | + |
| 92 | +void SecurityLoaderHelper::appendDest(const QJsonObject &dest) |
| 93 | +{ |
| 94 | + const QString dbusName = dest["DbusName"].toString(); |
| 95 | + const QString dbusPath = dest["DbusPath"].toString(); |
| 96 | + const QString dbusInterface = dest["DbusInterface"].toString(); |
| 97 | + |
| 98 | + if (dbusName.isEmpty() || dbusPath.isEmpty() || dbusInterface.isEmpty()) { |
| 99 | + qCWarning(secLoader) << "Skip invalid D-Bus destination:" << dest; |
| 100 | + return; |
| 101 | + } |
| 102 | + |
| 103 | + for (const auto &existing : m_destList) { |
| 104 | + const QJsonObject current = existing.toObject(); |
| 105 | + if (current["DbusName"] == dbusName && |
| 106 | + current["DbusPath"] == dbusPath && |
| 107 | + current["DbusInterface"] == dbusInterface) { |
| 108 | + return; |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + m_destList.append(dest); |
| 113 | + qCInfo(secLoader) << " Added:" << dbusName << dbusPath << dbusInterface; |
| 114 | +} |
| 115 | + |
| 116 | +void SecurityLoaderHelper::appendCurrentUserAccountsUserDest() |
| 117 | +{ |
| 118 | + const QString userPath = currentUserAccountsPath(); |
| 119 | + if (userPath.isEmpty()) { |
| 120 | + qCWarning(secLoader) << "Cannot resolve current user's Accounts.User path"; |
| 121 | + return; |
| 122 | + } |
| 123 | + |
| 124 | + QJsonObject dest; |
| 125 | + dest["DbusName"] = DSS_DBUS::accountsService; |
| 126 | + dest["DbusPath"] = userPath; |
| 127 | + dest["DbusInterface"] = DSS_DBUS::accountsUserInterface; |
| 128 | + appendDest(dest); |
| 129 | +} |
| 130 | + |
| 131 | +QString SecurityLoaderHelper::currentUserAccountsPath() const |
| 132 | +{ |
| 133 | + QDBusConnection systemBus = QDBusConnection::systemBus(); |
| 134 | + if (!systemBus.isConnected()) { |
| 135 | + qCWarning(secLoader) << "Cannot connect to system bus when resolving current user path"; |
| 136 | + return {}; |
| 137 | + } |
| 138 | + |
| 139 | + QDBusInterface accountsInterface(DSS_DBUS::accountsService, DSS_DBUS::accountsPath, DSS_DBUS::accountsService, systemBus); |
| 140 | + if (!accountsInterface.isValid()) { |
| 141 | + qCWarning(secLoader) << "Accounts interface invalid when resolving current user path:" |
| 142 | + << accountsInterface.lastError().message(); |
| 143 | + return {}; |
| 144 | + } |
| 145 | + |
| 146 | + QDBusReply<QString> reply = accountsInterface.call(QStringLiteral("FindUserById"), QString::number(getuid())); |
| 147 | + if (!reply.isValid()) { |
| 148 | + qCWarning(secLoader) << "FindUserById failed when resolving current user path:" |
| 149 | + << reply.error().message(); |
| 150 | + return {}; |
| 151 | + } |
| 152 | + |
| 153 | + return reply.value(); |
| 154 | +} |
| 155 | + |
| 156 | +bool SecurityLoaderHelper::performHandshake(int fd1, int fd2) |
| 157 | +{ |
| 158 | + if (m_destList.isEmpty()) { |
| 159 | + qCInfo(secLoader) << "No D-Bus interfaces loaded, skipping handshake"; |
| 160 | + return true; |
| 161 | + } |
| 162 | + |
| 163 | + qCInfo(secLoader) << "Performing loader handshake..."; |
| 164 | + |
| 165 | + QDBusConnection systemBus = QDBusConnection::systemBus(); |
| 166 | + if (!systemBus.isConnected()) { |
| 167 | + qCWarning(secLoader) << "Cannot connect to system bus"; |
| 168 | + return false; |
| 169 | + } |
| 170 | + // 偶现dbus的 unique name 发生变化,Qt的systemBus.baseService()实际上并未把dbus注册到总线上,要调用一下方法才行 |
| 171 | + systemBus.interface()->isServiceRegistered("org.freedesktop.DBus"); |
| 172 | + QString uniqueName = systemBus.baseService(); |
| 173 | + qCInfo(secLoader) << "System Bus UniqueName:" << uniqueName; |
| 174 | + |
| 175 | + QJsonObject request; |
| 176 | + request["UniqueName"] = uniqueName; |
| 177 | + request["DestList"] = m_destList; |
| 178 | + |
| 179 | + QJsonDocument doc(request); |
| 180 | + QByteArray jsonData = doc.toJson(QJsonDocument::Compact); |
| 181 | + |
| 182 | + qCInfo(secLoader) << "Sending request with" << m_destList.size() << "interfaces"; |
| 183 | + |
| 184 | + QFile fd1File; |
| 185 | + if (!fd1File.open(fd1, QIODevice::WriteOnly)) { |
| 186 | + qCWarning(secLoader) << "Cannot open fd1 for writing"; |
| 187 | + return false; |
| 188 | + } |
| 189 | + fd1File.write(jsonData); |
| 190 | + fd1File.close(); |
| 191 | + qCInfo(secLoader) << "Sent authorization request to loader"; |
| 192 | + |
| 193 | + QFile fd2File; |
| 194 | + if (!fd2File.open(fd2, QIODevice::ReadOnly)) { |
| 195 | + qCWarning(secLoader) << "Cannot open fd2 for reading"; |
| 196 | + return false; |
| 197 | + } |
| 198 | + |
| 199 | + QByteArray response = fd2File.readAll(); |
| 200 | + fd2File.close(); |
| 201 | + |
| 202 | + QJsonParseError parseError; |
| 203 | + QJsonDocument responseDoc = QJsonDocument::fromJson(response, &parseError); |
| 204 | + if (parseError.error != QJsonParseError::NoError) { |
| 205 | + qCWarning(secLoader) << "Invalid JSON response from loader:" << parseError.errorString(); |
| 206 | + return false; |
| 207 | + } |
| 208 | + |
| 209 | + QJsonObject result = responseDoc.object(); |
| 210 | + if (result["Result"].toBool()) { |
| 211 | + qCInfo(secLoader) << "Loader handshake completed successfully"; |
| 212 | + return true; |
| 213 | + } else { |
| 214 | + qCWarning(secLoader) << "Loader authorization response:" << result["Message"].toString(); |
| 215 | + return false; |
| 216 | + } |
| 217 | +} |
0 commit comments