From 314d0b841cd279717173d01bc1fceeb833a5cad1 Mon Sep 17 00:00:00 2001 From: guojiaqi Date: Fri, 10 Jul 2026 17:26:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(3rd-parties):=20=E4=BF=AE=E5=A4=8D=E5=8D=95?= =?UTF-8?q?=E4=BE=8B=E6=A8=A1=E5=9D=97=E5=AD=98=E5=9C=A8=E7=9A=84=E6=BC=8F?= =?UTF-8?q?=E6=B4=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change-Id: I0334606b6110da0bb4ecdc2834ad763d0ce6bcd4 --- .../qtsingleapplication/src/qtlocalpeer.cpp | 187 +++++++++++++++++- 1 file changed, 180 insertions(+), 7 deletions(-) diff --git a/3rd-parties/qtsingleapplication/src/qtlocalpeer.cpp b/3rd-parties/qtsingleapplication/src/qtlocalpeer.cpp index 7e99171..760b7e1 100644 --- a/3rd-parties/qtsingleapplication/src/qtlocalpeer.cpp +++ b/3rd-parties/qtsingleapplication/src/qtlocalpeer.cpp @@ -5,6 +5,7 @@ #include "qtlocalpeer.h" #include #include +#include #include #include @@ -15,6 +16,10 @@ typedef BOOL(WINAPI*PProcessIdToSessionId)(DWORD,DWORD*); static PProcessIdToSessionId pProcessIdToSessionId = 0; #endif #if defined(Q_OS_UNIX) +#include +#include +#include +#include #include #include #include @@ -31,6 +36,153 @@ namespace QtLP_Private { const char* QtLocalPeer::ack = "ack"; +#if defined(Q_OS_UNIX) +namespace { + +bool isPrivateRuntimeDirectory(const QString &path, uid_t uid) +{ + const QByteArray encodedPath = QFile::encodeName(path); + struct stat info; + if (::lstat(encodedPath.constData(), &info) != 0) { + return false; + } + + return S_ISDIR(info.st_mode) + && info.st_uid == uid + && (info.st_mode & (S_IRWXG | S_IRWXO)) == 0; +} + +QString secureRuntimeDirectory() +{ + const uid_t uid = ::getuid(); + const QString systemRuntimeDirectory = + QStringLiteral("/run/user/%1").arg(static_cast(uid)); + + // Do not trust XDG_RUNTIME_DIR here. The singleton path must not be + // redirected by a caller-controlled environment variable. + if (isPrivateRuntimeDirectory(systemRuntimeDirectory, uid)) { + return systemRuntimeDirectory; + } + + const struct passwd *passwordEntry = ::getpwuid(uid); + if (!passwordEntry || !passwordEntry->pw_dir || passwordEntry->pw_dir[0] == '\0') { + qFatal("QtLocalPeer: Failed to resolve the current user's home directory"); + } + + const QString homeDirectory = QString::fromLocal8Bit(passwordEntry->pw_dir); + const QString cacheDirectory = QDir(homeDirectory).filePath(QStringLiteral(".cache")); + const QString fallbackDirectory = + QDir(cacheDirectory).filePath(QStringLiteral("kylin-runtime")); + + if (!QDir().mkpath(cacheDirectory)) { + qFatal("QtLocalPeer: Failed to create cache directory: %s", + qPrintable(cacheDirectory)); + } + + const QByteArray encodedFallback = QFile::encodeName(fallbackDirectory); + if (::mkdir(encodedFallback.constData(), S_IRWXU) != 0 && errno != EEXIST) { + qFatal("QtLocalPeer: Failed to create private runtime directory %s: %s", + encodedFallback.constData(), strerror(errno)); + } + + struct stat info; + if (::lstat(encodedFallback.constData(), &info) != 0 + || !S_ISDIR(info.st_mode) + || info.st_uid != uid) { + qFatal("QtLocalPeer: Runtime directory is not a user-owned directory: %s", + encodedFallback.constData()); + } + + if (::chmod(encodedFallback.constData(), S_IRWXU) != 0 + || !isPrivateRuntimeDirectory(fallbackDirectory, uid)) { + qFatal("QtLocalPeer: Failed to secure runtime directory %s: %s", + encodedFallback.constData(), strerror(errno)); + } + + return fallbackDirectory; +} + +void prepareLockFile(const QString &lockFileName) +{ + const QByteArray encodedLockFile = QFile::encodeName(lockFileName); + int flags = O_CREAT | O_RDWR; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif +#ifdef O_NOFOLLOW + flags |= O_NOFOLLOW; +#endif + + const int fd = ::open(encodedLockFile.constData(), flags, S_IRUSR | S_IWUSR); + if (fd < 0) { + qFatal("QtLocalPeer: Failed to open lock file %s: %s", + encodedLockFile.constData(), strerror(errno)); + } + + struct stat info; + const bool validFile = ::fstat(fd, &info) == 0 + && S_ISREG(info.st_mode) + && info.st_uid == ::getuid(); + if (!validFile) { + ::close(fd); + qFatal("QtLocalPeer: Lock file is not a regular user-owned file: %s", + encodedLockFile.constData()); + } + + if (::fchmod(fd, S_IRUSR | S_IWUSR) != 0) { + const int savedErrno = errno; + ::close(fd); + qFatal("QtLocalPeer: Failed to secure lock file %s: %s", + encodedLockFile.constData(), strerror(savedErrno)); + } + + ::close(fd); +} + +unsigned long long processStartTime(pid_t pid) +{ +#if defined(Q_OS_LINUX) + const QByteArray statPath = + QByteArrayLiteral("/proc/") + QByteArray::number(pid) + QByteArrayLiteral("/stat"); + QFile statFile(QString::fromLocal8Bit(statPath)); + if (!statFile.open(QIODevice::ReadOnly)) { + return 0; + } + + const QByteArray content = statFile.readAll(); + const int closingParenthesis = content.lastIndexOf(')'); + if (closingParenthesis < 0) { + return 0; + } + + const QList fields = content.mid(closingParenthesis + 2).split(' '); + if (fields.size() < 20) { + return 0; + } + + return fields.at(19).toULongLong(); +#else + Q_UNUSED(pid) + return 0; +#endif +} + +void writeLockOwner(QtLP_Private::QtLockedFile &lockFile) +{ + const QByteArray content = QByteArray::number(::getpid()) + + ':' + QByteArray::number(::getuid()) + + ':' + QByteArray::number(processStartTime(::getpid())) + + '\n'; + + lockFile.seek(0); + lockFile.write(content); + lockFile.resize(content.size()); + lockFile.flush(); +} + +} // namespace +#endif + QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) : QObject(parent), id(appId) { @@ -65,11 +217,21 @@ QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId) #endif server = new QLocalServer(this); - QString lockName = QDir(QDir::tempPath()).absolutePath() - + QLatin1Char('/') + socketName - + QLatin1String("-lockfile"); - lockFile.setFileName(lockName); - lockFile.open(QIODevice::ReadWrite); +#if defined(Q_OS_UNIX) + const QString runtimeDirectory = secureRuntimeDirectory(); + socketName = QDir(runtimeDirectory).filePath(socketName); + const QString lockFileName = socketName + QLatin1String("-lockfile"); + prepareLockFile(lockFileName); +#else + const QString lockFileName = QDir(QDir::tempPath()).absolutePath() + + QLatin1Char('/') + socketName + QLatin1String("-lockfile"); +#endif + + lockFile.setFileName(lockFileName); + if (!lockFile.open(QIODevice::ReadWrite)) { + qFatal("QtLocalPeer: Failed to open lock file: %s", + qPrintable(lockFileName)); + } } @@ -82,11 +244,22 @@ bool QtLocalPeer::isClient() if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false)) return true; +#if defined(Q_OS_UNIX) + // The kernel lock is the singleton arbiter. The PID/UID/start-time data is + // metadata for diagnostics and is rewritten only by the lock owner. + writeLockOwner(lockFile); + + // Only the process holding the lock may remove a socket left by a crashed + // predecessor. socketName is an absolute path inside the private runtime + // directory, so listen() and connectToServer() use the same endpoint. + QLocalServer::removeServer(socketName); +#endif + bool res = server->listen(socketName); #if defined(Q_OS_UNIX) && (QT_VERSION >= QT_VERSION_CHECK(4,5,0)) - // ### Workaround + // Retry once in case a stale socket appeared between removal and listen. if (!res && server->serverError() == QAbstractSocket::AddressInUseError) { - QFile::remove(QDir::cleanPath(QDir::tempPath())+QLatin1Char('/')+socketName); + QLocalServer::removeServer(socketName); res = server->listen(socketName); } #endif -- Gitee