Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 187 additions & 0 deletions dconfig-center/dde-dconfig-daemon/appidresolver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "appidresolver.h"

#include <QDebug>
#include <QFile>
#include <QFileInfo>
#include <QRegularExpression>
#include <QDirIterator>

#ifdef Q_OS_LINUX
#include <sys/syscall.h>
#include <unistd.h>
#include <dbus/dbus.h>
#include <cerrno>
#include <memory>
#endif

Q_LOGGING_CATEGORY(cfLog, "dsg.config", QtInfoMsg);

AppIdResolver::AppIdResolver(QObject *parent)
: QObject(parent)
{
}

QString AppIdResolver::resolveAppId(const ConnServiceName &service, uint pid, uint uid)
{
// 检查缓存
if (m_cache.contains(service)) {
return m_cache.value(service);
}

QString appId;

#ifdef Q_OS_LINUX
// 首先尝试通过 AM Identify 获取
appId = identifyByAM(pid, uid);

// 如果 AM 不可用,fallback 到 /proc 解析
if (appId.isEmpty()) {
appId = fallbackFromProc(pid);
}
#else
// 非 Linux 平台直接 fallback
appId = fallbackFromProc(pid);
#endif

// 缓存结果
if (!appId.isEmpty()) {
m_cache.insert(service, appId);
}

return appId;
}

void AppIdResolver::clearCache(const ConnServiceName &service)
{
m_cache.remove(service);
}

#ifdef Q_OS_LINUX
QString AppIdResolver::identifyByAM(uint pid, uint uid)
{
// RAII helpers for libdbus-1 resources
auto dbusErrorDeleter = [](DBusError *err) { if (err) { dbus_error_free(err); delete err; } };
auto dbusConnDeleter = [](DBusConnection *c) { if (c) dbus_connection_unref(c); };
auto dbusMsgDeleter = [](DBusMessage *m) { if (m) dbus_message_unref(m); };

// 构造 session bus 地址(daemon 运行在 system bus,需跨总线连接用户 session bus)
QByteArray address = QString("unix:path=/run/user/%1/bus").arg(uid).toUtf8();

auto error = std::unique_ptr<DBusError, decltype(dbusErrorDeleter)>(new DBusError, dbusErrorDeleter);
dbus_error_init(error.get());

// 使用 dbus_connection_open_private 连接到用户的 session bus
// dbus_bus_get(DBUS_BUS_SESSION) 不可用,因为 daemon 没有自己的 session bus
DBusConnection *rawConn = dbus_connection_open_private(address.constData(), error.get());
if (!rawConn) {
qCDebug(cfLog) << "Failed to connect to session bus for uid" << uid << ":" << error->message;
return QString();
}
auto connGuard = std::unique_ptr<DBusConnection, decltype(dbusConnDeleter)>(rawConn, dbusConnDeleter);

// 注册连接(完成认证握手)
if (!dbus_bus_register(rawConn, error.get())) {
qCDebug(cfLog) << "Failed to register on session bus for uid" << uid << ":" << error->message;
return QString();
}

// 获取 pidfd
int pidfd = syscall(SYS_pidfd_open, pid, 0);
if (pidfd < 0) {
qCDebug(cfLog) << "Failed to get pidfd for pid" << pid << ":" << strerror(errno);
return QString();
}

// 创建 Identify 方法调用
DBusMessage *rawMsg = dbus_message_new_method_call(
"org.desktopspec.ApplicationManager1",
"/org/desktopspec/ApplicationManager1",
"org.desktopspec.ApplicationManager1",
"Identify");
if (!rawMsg) {
qCDebug(cfLog) << "Failed to create D-Bus message for Identify";
close(pidfd);
return QString();
}
auto msgGuard = std::unique_ptr<DBusMessage, decltype(dbusMsgDeleter)>(rawMsg, dbusMsgDeleter);

// 附加 pidfd 参数
if (!dbus_message_append_args(rawMsg, DBUS_TYPE_UNIX_FD, &pidfd, DBUS_TYPE_INVALID)) {
qCDebug(cfLog) << "Failed to append pidfd to D-Bus message";
close(pidfd);
return QString();
}

// 发送调用并等待回复
DBusMessage *rawReply = dbus_connection_send_with_reply_and_block(rawConn, rawMsg, 5000, error.get());
msgGuard.reset(); // msg consumed by the call
close(pidfd);

if (dbus_error_is_set(error.get())) {
qCDebug(cfLog) << "Identify call failed:" << error->message;
return QString();
}

if (!rawReply) {
qCDebug(cfLog) << "No reply received from Identify";
return QString();
}
auto replyGuard = std::unique_ptr<DBusMessage, decltype(dbusMsgDeleter)>(rawReply, dbusMsgDeleter);

// 解析回复
const char *appIdStr = nullptr;
if (!dbus_message_get_args(rawReply, error.get(), DBUS_TYPE_STRING, &appIdStr, DBUS_TYPE_INVALID)) {
qCDebug(cfLog) << "Failed to parse Identify reply:" << error->message;
return QString();
}

QString result = QString::fromUtf8(appIdStr);
qCDebug(cfLog) << "Got appId from AM:" << result << "for pid" << pid;
return result;
}
#endif

QString AppIdResolver::fallbackFromProc(uint pid)
{
// 从 /proc/{pid}/exe 获取进程路径
QString exePath = QFile::symLinkTarget(QString("/proc/%1/exe").arg(pid));

if (exePath.isEmpty()) {
// Fallback to cmdline
QFile cmdlineFile(QString("/proc/%1/cmdline").arg(pid));
if (cmdlineFile.open(QIODevice::ReadOnly)) {
QByteArray cmdline = cmdlineFile.readLine();
exePath = cmdline.split('\0').first();
}
}

if (exePath.isEmpty()) {
return QString();
}

// 格式化为 appId
// 参考 DSGApplication::formatAppId 的实现
QString appId = exePath;

// 移除前导路径分隔符并替换为 .
appId = appId.replace(QRegularExpression(QStringLiteral("^/+")), QString());
appId = appId.replace(QDir::separator(), QStringLiteral("."));

// 替换特殊字符为 -
static const QRegularExpression regex(QStringLiteral("[^\\w\\-\\.]"));
appId = appId.replace(regex, QStringLiteral("-"));

// 移除开头的 .
static const QString dotPrefix = QStringLiteral(".");
while (appId.startsWith(dotPrefix)) {
appId = appId.mid(dotPrefix.size());
}

qCDebug(cfLog) << "Fallback appId from /proc:" << appId << "for pid" << pid;

return appId;
}
36 changes: 36 additions & 0 deletions dconfig-center/dde-dconfig-daemon/appidresolver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include "dconfig_global.h"
#include <QObject>
#include <QHash>
#include <QDBusServiceWatcher>

class AppIdResolver : public QObject {
Q_OBJECT
public:
explicit AppIdResolver(QObject *parent = nullptr);
~AppIdResolver() override = default;

// 主入口:通过 service/pid/uid 解析标准 appId
// service: DBus service name,用于缓存 key
// pid: 调用方进程 ID
// uid: 调用方用户 ID
QString resolveAppId(const ConnServiceName &service, uint pid, uint uid);

// 清理指定 service 的缓存
void clearCache(const ConnServiceName &service);

private:
// 通过 AM Identify 获取 appId
QString identifyByAM(uint pid, uint uid);

// 从 /proc/exe 格式化 appId 作为 fallback
QString fallbackFromProc(uint pid);

// service -> appId 缓存
QHash<ConnServiceName, QString> m_cache;
};
92 changes: 90 additions & 2 deletions dconfig-center/dde-dconfig-daemon/dconfigconn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,24 @@ void DSGConfigConn::setResource(DSGConfigResource *resource)
m_resource = resource;
}

void DSGConfigConn::setConfigAppId(const QString &appId)
{
m_configAppId = appId;
}

void DSGConfigConn::setAppIdResolver(AppIdResolver *resolver)
{
m_resolver = resolver;
}

/*!
\brief 返回配置内容的所有配置项
\return
*/
QStringList DSGConfigConn::keyList() const
{
// keyList 是元信息,但为避免泄露 private key 名称,需要检查权限
// 暂时保持开放,因为客户端需要知道有哪些 key
return meta()->keyList();
}

Expand Down Expand Up @@ -117,6 +129,9 @@ void DSGConfigConn::setValue(const QString &key, const QDBusVariant &value)

if (!hasPermissionByUid(key))
return;

if (!hasPermissionByVisibility(key))
return;

const auto &v = decodeQDBusArgument(value.variant());
qCDebug(cfLog) << "Set value, key:" << key << ", now value:" << v << ", old value:" << file()->value(key, cache());
Expand All @@ -135,6 +150,9 @@ void DSGConfigConn::reset(const QString &key)
if (!contains(key))
return;

if (!hasPermissionByVisibility(key))
return;

qCDebug(cfLog) << "Reset value, key:" << key << ", old value:" << file()->value(key, cache());
if(!file()->setValue(key, QVariant(), getAppid(), cache()))
return;
Expand All @@ -158,6 +176,9 @@ QDBusVariant DSGConfigConn::value(const QString &key)

if (!hasPermissionByUid(key))
return QDBusVariant();

if (!hasPermissionByVisibility(key))
return QDBusVariant();

// Try to get value from cache.
auto value = file()->cacheValue(cache(), key);
Expand Down Expand Up @@ -203,6 +224,9 @@ bool DSGConfigConn::isDefaultValue(const QString &key)
{
if (!contains(key))
return false;

if (!hasPermissionByVisibility(key))
return false;

// Try to get value from cache.
auto value = file()->cacheValue(cache(), key);
Expand Down Expand Up @@ -238,19 +262,47 @@ int DSGConfigConn::flags(const QString &key)
return static_cast<int>(meta()->flags(key));
}

/*!
\brief 获取调用方的进程路径(用于日志和 setValue 记录)
\return 进程可执行文件路径
*/
QString DSGConfigConn::getAppid() const
{
if (calledFromDBus()) {
const QString &service = message().service();
if (m_lastService != service) {
const_cast<DSGConfigConn *>(this)->m_lastService = service;
const_cast<DSGConfigConn *>(this)->m_appName = getProcessNameByPid(connection().interface()->servicePid(service));
m_lastService = service;
m_appName = getProcessNameByPid(connection().interface()->servicePid(service));
}
return m_appName;
}
return QString("testappid");
}

/*!
\brief 获取调用方的标准 appId(用于 private 权限校验)
\return 标准 appId,如 org.deepin.dde.control-center
*/
QString DSGConfigConn::getCallerAppId() const
{
if (!calledFromDBus())
return QString();

if (!m_resolver) {
qCWarning(cfLog) << "AppIdResolver is not set, cannot get caller appId";
return QString();
}

const QString &service = message().service();
if (m_lastAppIdService != service) {
m_lastAppIdService = service;
uint pid = connection().interface()->servicePid(service);
uint uid = connection().interface()->serviceUid(service);
m_callerAppId = m_resolver->resolveAppId(service, pid, uid);
}
return m_callerAppId;
}

bool DSGConfigConn::contains(const QString &key)
{
if (containsWithoutProp(key))
Expand Down Expand Up @@ -301,3 +353,39 @@ bool DSGConfigConn::hasPermissionByUid(const QString &key) const
}
return hasPermission;
}

/*!
\brief 检查 private 权限
当配置项 visibility 为 private 时,仅允许配置所属 appId 的应用访问
\a key 配置项名称
\return 是否有权限
*/
bool DSGConfigConn::hasPermissionByVisibility(const QString &key) const
{
if (!calledFromDBus())
return true;

// public 配置允许所有应用访问
if (meta()->visibility(key) == DConfigFile::Public)
return true;

// private 配置:检查调用方 appId 是否匹配配置的 appId
const QString &callerAppId = getCallerAppId();
const QString &configAppId = m_configAppId;

// generic 配置(无 appId)允许访问
if (configAppId.isEmpty() || configAppId == VirtualInterAppId)
return true;

// appId 匹配则允许
if (callerAppId == configAppId)
return true;

// 拒绝访问
QString errorMsg = QString("[%1] No permission to access private config item [%2] in [%3], "
"owner appId is [%4].")
.arg(callerAppId).arg(key).arg(m_key).arg(configAppId);
sendErrorReply(QDBusError::AccessDenied, errorMsg);
qWarning() << qPrintable(errorMsg);
return false;
}
Loading
Loading