Skip to content

Commit 2d704d2

Browse files
committed
fix: correct directory ownership when creating cache dirs with sudo
When running `sudo deepin-log-viewer --reportcoredump`, directories created under ~/.cache/deepin/deepin-log-viewer/ are owned by root, causing subsequent normal launches to fail writing to them. Add Utils::fixDirOwnership() to chown newly created directories to the real logged-in user (detected via lstat(homePath)) when getuid() differs from the real user UID. Apply it to all mkpath call sites that create cache directories under the user's home: updateRepeatCoredumpExePaths, UserLogAccess constructor, and UserLogAccess::createExtractSubdir. Harden fixDirOwnership() against symlink attacks: walk the target path from root using openat(O_DIRECTORY | O_NOFOLLOW) at each level, then fchown() the resulting fd. This prevents a non-privileged user from pre-creating a symlink along the path to trick the privileged sudo process into chowning arbitrary targets outside the home directory. Add error handling for createExtractSubdir() returning empty string (mkpath failure) at all three call sites: skip unzip and fall back to returning original file paths directly, preventing silent file loss. Also add qCWarning logging for unchecked mkpath return values across the project. PMS: TASK-395141
1 parent 0470b25 commit 2d704d2

7 files changed

Lines changed: 97 additions & 16 deletions

File tree

‎application/dbusproxy/userlogaccess.cpp‎

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ UserLogAccess::UserLogAccess(QObject *parent)
4242
{
4343
// 初始化解压根目录:~/.cache/deepin/deepin-log-viewer/user-log
4444
m_extractRoot = Utils::homePath + QStringLiteral("/.cache/deepin/deepin-log-viewer/user-log");
45-
QDir().mkpath(m_extractRoot);
45+
if (!QDir().mkpath(m_extractRoot)) {
46+
qCWarning(logUserLogAccess) << "Failed to create extract root directory:" << m_extractRoot;
47+
} else {
48+
// mkpath 可能同时创建父目录 deepin-log-viewer,两处均需修正属主
49+
Utils::fixDirOwnership(Utils::homePath + QStringLiteral("/.cache/deepin/deepin-log-viewer"));
50+
Utils::fixDirOwnership(m_extractRoot);
51+
}
4652

4753
// 清理上次残留的解压子目录
4854
QDir rootDir(m_extractRoot);
@@ -74,7 +80,11 @@ QString UserLogAccess::createExtractSubdir()
7480
// 每次调用创建独立子目录,避免并发互删
7581
const QString subdir = m_extractRoot + QDir::separator()
7682
+ QStringLiteral("extract-") + QUuid::createUuid().toString(QUuid::WithoutBraces);
77-
QDir().mkpath(subdir);
83+
if (!QDir().mkpath(subdir)) {
84+
qCWarning(logUserLogAccess) << "Failed to create extract subdirectory:" << subdir;
85+
return QString();
86+
}
87+
Utils::fixDirOwnership(subdir);
7888
return subdir;
7989
}
8090

@@ -277,10 +287,10 @@ QStringList UserLogAccess::getFileInfo(const QString &file, bool unzip)
277287

278288
// 独立解压子目录
279289
QString extractDir = createExtractSubdir();
280-
QString tempFileTemplate = extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
290+
QString tempFileTemplate = extractDir.isEmpty() ? QString() : extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
281291

282292
for (int i = 0; i < fileList.count(); i++) {
283-
if (fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
293+
if (!tempFileTemplate.isEmpty() && fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
284294
QString unzipFile = unzipToTempFile(fileList[i].absoluteFilePath(), tempFileTemplate);
285295
if (!unzipFile.isEmpty())
286296
fileNamePath.append(unzipFile);
@@ -308,10 +318,10 @@ QStringList UserLogAccess::getFileInfo(const QString &file, bool unzip)
308318
QFileInfoList fileList = dir.entryInfoList();
309319

310320
QString extractDir = createExtractSubdir();
311-
QString tempFileTemplate = extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
321+
QString tempFileTemplate = extractDir.isEmpty() ? QString() : extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
312322

313323
for (int i = 0; i < fileList.count(); i++) {
314-
if (fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
324+
if (!tempFileTemplate.isEmpty() && fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
315325
QString unzipFile = unzipToTempFile(fileList[i].absoluteFilePath(), tempFileTemplate);
316326
if (!unzipFile.isEmpty())
317327
fileNamePath.append(unzipFile);
@@ -350,10 +360,10 @@ QStringList UserLogAccess::getOtherFileInfo(const QString &file, bool unzip)
350360
QFileInfoList fileList = dir.entryInfoList();
351361

352362
QString extractDir = createExtractSubdir();
353-
QString tempFileTemplate = extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
363+
QString tempFileTemplate = extractDir.isEmpty() ? QString() : extractDir + QDir::separator() + "Log_extract_XXXXXX.txt";
354364

355365
for (int i = 0; i < fileList.count(); i++) {
356-
if (fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
366+
if (!tempFileTemplate.isEmpty() && fileList[i].suffix().compare("gz", Qt::CaseInsensitive) == 0 && unzip) {
357367
QString unzipFile = unzipToTempFile(fileList[i].absoluteFilePath(), tempFileTemplate);
358368
if (!unzipFile.isEmpty())
359369
fileNamePath.append(unzipFile);

‎application/logbackend.cpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2079,7 +2079,8 @@ bool LogBackend::getOutDirPath(const QString &path)
20792079
QDir dir(tmpPath);
20802080
if (!dir.exists()) {
20812081
qCDebug(logApp) << "LogBackend::getOutDirPath dir does not exist, creating directory";
2082-
dir.mkpath(dir.absolutePath());
2082+
if (!dir.mkpath(dir.absolutePath()))
2083+
qCWarning(logApp) << "Failed to create output directory:" << dir.absolutePath();
20832084
m_newDir = dir.exists();
20842085
if (m_newDir)
20852086
tmpPath = dir.absolutePath();

‎application/logsettings.cpp‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2019 - 2022 UnionTech Software Technology Co., Ltd.
1+
// SPDX-FileCopyrightText: 2019-2026 UnionTech Software Technology Co., Ltd.
22
//
33
// SPDX-License-Identifier: GPL-3.0-or-later
44

@@ -40,7 +40,8 @@ LogSettings::LogSettings(QObject *parent)
4040
QDir infoPath(Utils::getConfigPath());
4141
if (!infoPath.exists()) {
4242
qCDebug(logApp) << "Info path does not exist, creating it";
43-
infoPath.mkpath(Utils::getConfigPath());
43+
if (!infoPath.mkpath(Utils::getConfigPath()))
44+
qCWarning(logApp) << "Failed to create config directory:" << Utils::getConfigPath();
4445
}
4546

4647
m_configPath = infoPath.filePath("wininfo-config.conf");

‎application/utils.cpp‎

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111

1212
#include <math.h>
1313
#include <pwd.h>
14+
#include <unistd.h>
15+
#include <sys/stat.h>
16+
#include <errno.h>
17+
#include <fcntl.h>
1418

1519
#include <QUrl>
1620
#include <QDir>
@@ -105,7 +109,8 @@ QString Utils::getAppDataPath()
105109

106110
QString path = dir.filePath(qApp->applicationName());
107111
if (!dir.exists(path))
108-
dir.mkpath(path);
112+
if (!dir.mkpath(path))
113+
qCWarning(logApp) << "Failed to create app data directory:" << path;
109114
qCDebug(logApp) << "App data path:" << path;
110115
return path;
111116
}
@@ -353,6 +358,59 @@ QString Utils::mkMutiDir(const QString &path)
353358
return parentDir + "/" + dirname;
354359
}
355360

361+
void Utils::fixDirOwnership(const QString &dirPath)
362+
{
363+
// 仅处理 homePath 目录下的路径,避免误改其他目录属主
364+
if (!dirPath.startsWith(Utils::homePath + QLatin1Char('/'))) {
365+
qCWarning(logApp) << "fixDirOwnership: dirPath is not under homePath, skip:" << dirPath;
366+
return;
367+
}
368+
369+
// 使用 lstat 获取 homePath 属主,不跟随符号链接
370+
struct stat st;
371+
if (lstat(Utils::homePath.toUtf8().constData(), &st) != 0) {
372+
qCWarning(logApp) << "fixDirOwnership: failed to lstat homePath:" << Utils::homePath << "error:" << strerror(errno);
373+
return;
374+
}
375+
uid_t realUID = st.st_uid;
376+
gid_t realGID = st.st_gid;
377+
if (getuid() == realUID) {
378+
return;
379+
}
380+
381+
// 从根目录逐级 openat(O_DIRECTORY | O_NOFOLLOW) 打开目标路径,
382+
// 任一组件是符号链接则 openat 失败,拒绝继续,防止 symlink 攻击。
383+
int fd = open("/", O_DIRECTORY);
384+
if (fd < 0) {
385+
qCWarning(logApp) << "fixDirOwnership: failed to open root directory:" << strerror(errno);
386+
return;
387+
}
388+
389+
QByteArray pathBytes = dirPath.toUtf8();
390+
char *path = pathBytes.data();
391+
char *saveptr = nullptr;
392+
char *component = strtok_r(path, "/", &saveptr);
393+
while (component != nullptr) {
394+
int nextFd = openat(fd, component, O_DIRECTORY | O_NOFOLLOW);
395+
if (nextFd < 0) {
396+
qCWarning(logApp) << "fixDirOwnership: refused to traverse component" << component
397+
<< "(possible symlink), error:" << strerror(errno);
398+
close(fd);
399+
return;
400+
}
401+
close(fd);
402+
fd = nextFd;
403+
component = strtok_r(nullptr, "/", &saveptr);
404+
}
405+
406+
if (fchown(fd, realUID, realGID) != 0) {
407+
qCWarning(logApp) << "fixDirOwnership: failed to fchown" << dirPath
408+
<< "to uid:" << realUID << "gid:" << realGID
409+
<< "error:" << strerror(errno);
410+
}
411+
close(fd);
412+
}
413+
356414
bool Utils::checkAuthorization(const QString &actionId, qint64 applicationPid)
357415
{
358416
qCDebug(logApp) << "Checking authorization for action:" << actionId << "pid:" << applicationPid;
@@ -583,7 +641,11 @@ void Utils::updateRepeatCoredumpExePaths(const QList<LOG_REPEAT_COREDUMP_INFO> &
583641
// 目录不存在,则创建目录
584642
if (!QFileInfo::exists(fi.absolutePath())) {
585643
QDir dir;
586-
dir.mkpath(fi.absolutePath());
644+
if (!dir.mkpath(fi.absolutePath())) {
645+
qCWarning(logApp) << "Failed to create directory:" << fi.absolutePath();
646+
} else {
647+
Utils::fixDirOwnership(fi.absolutePath());
648+
}
587649
}
588650

589651
// 确定当前时间范围的崩溃数据高频重复路径

‎application/utils.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class Utils : public QObject
8383
// 查找所有物理网络接口
8484
static QStringList getPhysicalInterfaces();
8585
static QStringList expandPathWithWildcardIterator(const QString &pathWithWildcard);
86+
// 修正目录属主:当进程 UID 与真实登录用户 UID 不一致时,chown 为真实用户
87+
static void fixDirOwnership(const QString &dirPath);
8688
// 执行cmd命令
8789
static QByteArray executeCmd(const QString& cmd, const QStringList& args = QStringList(), const QString& workPath = QString());
8890
static QByteArray processCmdWithArgs(const QString &cmdStr, const QString &workPath, const QStringList &args);

‎logViewerService/main.cpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ int main(int argc, char *argv[])
5050
.arg(qApp->applicationName());
5151

5252
if (!dirCheck.exists(LogPath)) {
53-
dirCheck.mkpath(LogPath);
53+
if (!dirCheck.mkpath(LogPath))
54+
qCWarning(logService) << "Failed to create log directory:" << LogPath;
5455
}
5556
QString serviceLogPath=LogPath+QString("%1.log").arg(qApp->applicationName());
5657
Dtk::Core::DLogManager::setlogFilePath(serviceLogPath);

‎logViewerService/opslogexport.cpp‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,10 @@ bool OpsLogExport::path_exists(const string &path)
190190

191191
bool OpsLogExport::create_directories(const string &path)
192192
{
193-
return QDir(QString::fromStdString(path)).mkpath(".");
193+
bool ok = QDir(QString::fromStdString(path)).mkpath(".");
194+
if (!ok)
195+
qWarning() << "Failed to create directories:" << QString::fromStdString(path);
196+
return ok;
194197
}
195198

196199
void OpsLogExport::copy_file_or_dir(const string &src, const string &dst_dir)
@@ -207,7 +210,8 @@ void OpsLogExport::copy_file_or_dir(const string &src, const string &dst_dir)
207210
if (srcInfo.isSymLink()) return;
208211
if (srcInfo.isFile()) {
209212
// 单个文件:确保目标目录存在后用 QFile::copy
210-
QDir().mkpath(qDst);
213+
if (!QDir().mkpath(qDst))
214+
qWarning() << "Failed to create directory:" << qDst;
211215
QString dstFile = qDst + "/" + srcInfo.fileName();
212216
QFile::remove(dstFile); // QFile::copy 要求目标不存在
213217
QFile::copy(qSrc, dstFile);

0 commit comments

Comments
 (0)