diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.cpp b/deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.cpp index ea1e1eee..022057c1 100644 --- a/deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.cpp +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/controlinterface.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019 ~ 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,6 +10,7 @@ #include "enablesqlmanager.h" #include "enableutils.h" #include "wakeuputils.h" +#include "securityutils.h" #include "DDLog.h" #include @@ -487,6 +488,13 @@ bool ControlInterface::aptUpdate() bool ControlInterface::authorizedEnable(const QString &hclass, const QString &name, const QString &path, const QString &unique_id, bool enable_device, const QString strDriver) { qCDebug(appLog) << "Authorized enable operation:" << hclass << name << path << "enable:" << enable_device; + + // 安全校验:防止 sysfs 路径穿越攻击 + if (!isSafeSysfsPath(path)) { + qCWarning(appLog) << "authorizedEnable: unsafe sysfs path rejected:" << path; + return false; + } + // 通过authorized文件启用禁用设备 // 0:表示禁用 ,1:表示启用 QFile file("/sys" + path + QString("/authorized")); @@ -531,6 +539,13 @@ bool ControlInterface::authorizedEnable(const QString &hclass, const QString &na bool ControlInterface::removeEnable(const QString &hclass, const QString &name, const QString &path, const QString &unique_id, bool enable, const QString strDriver) { qCDebug(appLog) << "Remove enable operation:" << hclass << name << path << "enable:" << enable; + + // 安全校验:防止 sysfs 路径穿越攻击 + if (!isSafeSysfsPath(path)) { + qCWarning(appLog) << "removeEnable: unsafe sysfs path rejected:" << path; + return false; + } + if (enable) { // 1. 先rescan 向rescan写入1,则重新加载 QFile file("/sys/bus/pci/rescan"); diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/drivermanager.cpp b/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/drivermanager.cpp index a832b640..d35098ab 100644 --- a/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/drivermanager.cpp +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/drivermanager.cpp @@ -7,6 +7,7 @@ #include "drivermanager.h" #include "utils.h" #include "modcore.h" +#include "securityutils.h" #include "aptinstaller.h" #include "driverinstallerapt.h" //#include "DeviceInfoManager.h" @@ -265,7 +266,16 @@ bool DriverManager::installDriver(const QString &filepath) } else { //已判断文件是否存在所以必然存在文件名 QString filename = fileinfo.fileName(); - QString installdir = QString("/lib/modules/%1/custom/%2").arg(Utils::kernelRelease()).arg(mp_modcore->modGetName(filepath)); + QString modName = mp_modcore->modGetName(filepath); + + // 安全校验:模块名合法性 + 安装路径边界检查 + if (!validateModNameForInstall(modName, Utils::kernelRelease(), errmsg)) { + qCWarning(appLog) << "installDriver: security validation failed:" << errmsg; + sigFinished(false, errmsg); + return false; + } + + QString installdir = QString("/lib/modules/%1/custom/%2").arg(Utils::kernelRelease()).arg(modName); QDir installDir(installdir); //判断安装路径是否已存在,如果不存在先创建安装目录 if (installDir.exists() || diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/modcore.cpp b/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/modcore.cpp index c87caca9..f482d5df 100644 --- a/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/modcore.cpp +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/drivercontrol/modcore.cpp @@ -5,6 +5,7 @@ #ifndef DISABLE_DRIVER #include "modcore.h" +#include "securityutils.h" #include "DDLog.h" #include @@ -501,6 +502,12 @@ void ModCore::updateInitramfs() void ModCore::rmModLoadedOnBoot(const QString &modName) { + // 输入白名单校验:防止路径穿越攻击 + if (!isValidModName(modName)) { + qCWarning(appLog) << "Invalid module name rejected for load-on-boot removal:" << modName; + return; + } + QString conffile = QString(LOADONBOOT_FILENAME_TEMPLETE).arg(modName); QDir bootloaddir(LOADONBOOT_PROBE_DIR); QStringList bootconfs = bootloaddir.entryList(QDir::Files | QDir::NoDotAndDotDot | QDir::Readable); @@ -598,6 +605,12 @@ bool ModCore::isModFile(const QString &filePath) */ bool ModCore::addModBlackList(const QString &modName) { + // 输入白名单校验:防止路径穿越攻击 + if (!isValidModName(modName)) { + qCWarning(appLog) << "Invalid module name rejected for blacklist:" << modName; + return false; + } + QString blacklistdir; if (QFileInfo::exists(BLACKLISTT_PROBE_DIR_ETC)) { blacklistdir = BLACKLISTT_PROBE_DIR_ETC; @@ -610,6 +623,13 @@ bool ModCore::addModBlackList(const QString &modName) //sample: /etc/modprobe.d/blacklist-bnep-drivermanager.conf QString basefilename = QString(BLACKLIST_FILENAME_TEMPLETE).arg(modName); QString filepath = QString("%1/%2").arg(blacklistdir).arg(basefilename); + + // 路径边界检查:确保最终路径在预期目录内,防止符号链接穿越 + if (!isPathWithinDirectory(filepath, blacklistdir)) { + qCWarning(appLog) << "Path traversal attempt detected in addModBlackList:" << filepath; + return false; + } + QFile blackfile(filepath); if (!blackfile.open(QIODevice::ReadWrite)) return false; @@ -635,6 +655,12 @@ bool ModCore::addModBlackList(const QString &modName) */ void ModCore::rmFromBlackList(const QString &modName) { + // 输入白名单校验:防止路径穿越攻击 + if (!isValidModName(modName)) { + qCWarning(appLog) << "Invalid module name rejected for blacklist removal:" << modName; + return; + } + //移除黑名单配置,查找目录BLACKLISTT_PROBE_DIR_ETC 和BLACKLISTT_PROBE_DIR_USR_LIB //1.通过本应用安装可以移除文件 //2.如果要移除其它的只能遍历其它配置文件进行删除 @@ -674,9 +700,22 @@ void ModCore::rmFromBlackList(const QString &modName) */ bool ModCore::setModLoadedOnBoot(const QString &modName) { + // 输入白名单校验:防止路径穿越攻击 + if (!isValidModName(modName)) { + qCWarning(appLog) << "Invalid module name rejected for load-on-boot:" << modName; + return false; + } + QFileInfo fileinfo(LOADONBOOT_PROBE_DIR); QString strfilename = QString(LOADONBOOT_FILENAME_TEMPLETE).arg(modName); QString filepath = fileinfo.dir().absoluteFilePath(strfilename); + + // 路径边界检查:确保最终路径在预期目录内 + if (!isPathWithinDirectory(filepath, LOADONBOOT_PROBE_DIR)) { + qCWarning(appLog) << "Path traversal attempt detected in setModLoadedOnBoot:" << filepath; + return false; + } + QFile bootconf(filepath); if (!bootconf.open(QIODevice::ReadWrite)) return false; @@ -684,4 +723,5 @@ bool ModCore::setModLoadedOnBoot(const QString &modName) return true; } + #endif // DISABLE_DRIVER diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.cpp b/deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.cpp index a2440444..eb990c31 100644 --- a/deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.cpp +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/enablecontrol/enableutils.cpp @@ -4,6 +4,7 @@ #include "enableutils.h" #include "enablesqlmanager.h" +#include "securityutils.h" #include "DDLog.h" #include @@ -113,6 +114,11 @@ void EnableUtils::disableOutDevice(const QString &info) // 先判断设备是否被记录在数据库,如果在则禁用 if (EnableSqlManager::getInstance()->uniqueIDExisted(uniqueID)) { + // 安全校验:防止 sysfs 路径穿越攻击 + if (!isSafeSysfsPath(path)) { + qCWarning(appLog) << "disableOutDevice: unsafe sysfs path rejected:" << path; + continue; + } QFile file("/sys" + path + QString("/authorized")); if (!file.open(QIODevice::ReadWrite)) { return; @@ -144,6 +150,11 @@ void EnableUtils::disableInDevice() QList > lstRemovePair; EnableSqlManager::getInstance()->removePathUniqueIDList(lstRemovePair); for (QList>::iterator it = lstRemovePair.begin() ; it != lstRemovePair.end(); ++it) { + // 安全校验:防止 sysfs 路径穿越攻击 + if (!isSafeSysfsPath((*it).first)) { + qCWarning(appLog) << "disableInDevice: unsafe sysfs path rejected:" << (*it).first; + continue; + } QString pathT = "/sys" + (*it).first + QString("/remove"); if (!QFile::exists(pathT)) { pathT = "/sys" + (*it).first + QString("/reset"); diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.cpp b/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.cpp new file mode 100644 index 00000000..bf39fd96 --- /dev/null +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.cpp @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * @file securityutils.cpp + * @brief 路径安全校验公共函数实现 + */ + +#include "securityutils.h" + +#include +#include +#include +#include + +bool isValidModName(const QString &modName) +{ + if (modName.isEmpty()) + return false; + // 仅允许字母、数字、下划线和连字符,禁止路径穿越字符和空白符 + static const QRegularExpression kValidModName(QLatin1String("^[a-zA-Z0-9_-]+$")); + return kValidModName.match(modName).hasMatch(); +} + +bool isPathWithinDirectory(const QString &filePath, const QString &baseDir) +{ + if (filePath.isEmpty() || baseDir.isEmpty()) + return false; + + QFileInfo fileInfo(filePath); + QString canonicalPath = fileInfo.canonicalFilePath(); + if (canonicalPath.isEmpty()) + return false; + + QFileInfo dirInfo(baseDir); + QString canonicalDir = dirInfo.canonicalFilePath(); + if (canonicalDir.isEmpty()) + return false; + + // 确保基准目录以 "/" 结尾,防止 "/etc" 被 "/etc-modprobe.d" 前缀匹配 + if (!canonicalDir.endsWith(QLatin1Char('/'))) + canonicalDir += QLatin1Char('/'); + + return canonicalPath.startsWith(canonicalDir); +} + +bool isSafeSysfsPath(const QString &relativePath) +{ + if (relativePath.isEmpty()) + return false; + + // 第一层:拒绝包含 ".." 的路径,防止路径穿越 + if (relativePath.contains(QLatin1String(".."))) + return false; + + // 第二层:构建完整路径并验证其 canonical path 以 /sys 开头 + QString fullPath = QStringLiteral("/sys") + relativePath; + QFileInfo fileInfo(fullPath); + QString canonicalPath = fileInfo.canonicalFilePath(); + + if (canonicalPath.isEmpty()) + return false; + + // 确保 canonical path 以 /sys/ 开头,防止符号链接穿越到 /sys 外部 + if (!canonicalPath.startsWith(QLatin1String("/sys/"))) + return false; + + return true; +} + +bool validateModNameForInstall(const QString &modName, const QString &kernelRelease, QString &errMsg) +{ + // 第一层:模块名白名单校验,确保不含路径分隔符或穿越字符 + if (!isValidModName(modName)) { + errMsg = QString("Invalid module name: %1").arg(modName); + return false; + } + + // 第二层:校验构造的安装路径是否在预期目录下 + // 使用 QDir::cleanPath() 规范化路径后检查前缀(适用于尚未创建的目录) + QString installdir = QString("/lib/modules/%1/custom/%2").arg(kernelRelease).arg(modName); + QString normalizedPath = QDir::cleanPath(installdir); + QString expectedPrefix = QString("/lib/modules/%1/custom/").arg(kernelRelease); + + if (!normalizedPath.startsWith(expectedPrefix)) { + errMsg = "Path traversal attempt detected"; + return false; + } + + return true; +} diff --git a/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.h b/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.h new file mode 100644 index 00000000..10e1fdff --- /dev/null +++ b/deepin-devicemanager-server/deepin-devicecontrol/src/securityutils.h @@ -0,0 +1,65 @@ +// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +/** + * @file securityutils.h + * @brief 路径安全校验公共函数库 + * + * 提供统一的路径穿越防护校验函数,供所有模块使用。 + * 所有涉及用户输入构造文件路径的操作都应使用这些函数进行校验。 + */ + +#ifndef SECURITYUTILS_H +#define SECURITYUTILS_H + +#include + +/** + * @brief 校验模块名是否合法(仅允许字母、数字、下划线和连字符) + * + * Linux 内核模块名本身只允许字母、数字、下划线和连字符。 + * 此函数用于防止通过构造恶意模块名进行路径穿越攻击。 + * + * @param modName 模块名称 + * @return true:合法 false:非法 + */ +bool isValidModName(const QString &modName); + +/** + * @brief 校验解析后的真实路径是否在预期目录内(防止路径穿越) + * + * 使用 QFileInfo::canonicalFilePath() 解析符号链接和 .. 路径, + * 然后验证最终路径是否在预期的基准目录内。 + * + * @param filePath 构造的文件路径(可以是相对或绝对路径) + * @param baseDir 预期的基准目录(必须是绝对路径) + * @return true:合法 false:发生路径穿越 + */ +bool isPathWithinDirectory(const QString &filePath, const QString &baseDir); + +/** + * @brief 校验 sysfs 路径是否安全,防止路径穿越攻击 + * + * sysfs 虚拟文件系统中的路径通常以 /sys 开头。 + * 此函数用于防止通过构造恶意路径访问 /sys 外部的系统文件。 + * + * @param relativePath 相对于 /sys 的设备路径(不应包含 .. 或绝对路径成分) + * @return true:安全 false:不安全 + */ +bool isSafeSysfsPath(const QString &relativePath); + +/** + * @brief 校验模块名是否可用于驱动安装路径构造 + * + * 综合检查:模块名合法性 + 安装目录路径边界检查。 + * 用于 installDriver 等需要安装驱动的场景。 + * + * @param modName 模块名称 + * @param kernelRelease 内核版本号(来自 /lib/modules//custom/) + * @param errMsg 如果验证失败,通过此参数返回错误信息 + * @return true:验证通过 false:验证失败(errMsg 包含原因) + */ +bool validateModNameForInstall(const QString &modName, const QString &kernelRelease, QString &errMsg); + +#endif // SECURITYUTILS_H