File indexing completed on 2024-04-21 05:26:43
0001 /* 0002 SPDX-License-Identifier: GPL-2.0-or-later 0003 SPDX-FileCopyrightText: 2021 Harald Sitter <sitter@kde.org> 0004 */ 0005 0006 #include "linuxprocmapsparser.h" 0007 0008 #include <sys/stat.h> 0009 #include <sys/types.h> 0010 #include <unistd.h> 0011 0012 #include <cerrno> 0013 #include <cstring> 0014 0015 #include <QByteArrayList> 0016 #include <QDebug> 0017 #include <QFile> 0018 #include <QRegularExpression> 0019 #include <QStorageInfo> 0020 0021 #include "drkonqi_debug.h" 0022 0023 using namespace Qt::StringLiterals; 0024 0025 bool LinuxProc::isLibraryPath(const QString &path) 0026 { 0027 // This intentionally matches potential suffixes, i.e. "/usr/lib/foo.so.0" but also "foo.so (deleted)" 0028 static QRegularExpression soExpression(QStringLiteral("(?<path>.+\\.(so|py)([^/]*))$")); 0029 const auto soMatch = soExpression.match(path); 0030 return soMatch.isValid() && soMatch.hasMatch() && !soMatch.captured(u"path").isEmpty(); 0031 } 0032 0033 bool LinuxProc::hasMapsDeletedFiles(const QString &exePathString, const QByteArray &maps, Check check) 0034 { 0035 const QByteArray exePath = QFile::encodeName(exePathString); 0036 const QByteArrayList lines = maps.split('\n'); 0037 for (const auto &line : lines) { 0038 if (line.isEmpty()) { 0039 continue; 0040 } 0041 // Walk string by tokens. This is by far the easiest way to parse the format as anything after 0042 // the first 5 fields (minus the tokens) is the pathname. The pathname may be nothing, or contain more 0043 // spaces in turn. Qt has no convenient API for this, use strtok. 0044 0045 QByteArray mutableLine = line; 0046 // address 0047 std::ignore = strtok(mutableLine.data(), " "); 0048 // perms 0049 std::ignore = strtok(nullptr, " "); 0050 // offset 0051 std::ignore = strtok(nullptr, " "); 0052 // dev 0053 std::ignore = strtok(nullptr, " "); 0054 // inode 0055 const QByteArray inodeString(strtok(nullptr, " ")); 0056 bool ok = false; 0057 const ino_t inode = inodeString.toULongLong(&ok); 0058 if (!ok) { 0059 qCWarning(DRKONQI_LOG) << "inode appears to not be a ulonglong"; 0060 } 0061 // remainder is the pathname 0062 const QByteArray pathname = QByteArray(strtok(nullptr, "\n")).simplified(); // simplify to make evaluation easier 0063 0064 if (pathname.isEmpty() || pathname.at(0) != QLatin1Char('/')) { 0065 // Could be pseudo entry like [heap] or anonymous region. 0066 continue; 0067 } 0068 0069 qCDebug(DRKONQI_LOG) << "Checking for file status" << pathname; 0070 0071 if (pathname.startsWith(QByteArrayLiteral("/memfd"))) { 0072 // Qml.so's JIT shows up under memfd. This is a false positive as it isn't a real path in the 0073 // file system. Skip over it. 0074 continue; 0075 } 0076 0077 const QByteArray deletedMarker = QByteArrayLiteral(" (deleted)"); 0078 // We filter only .so files to ensure that we don't trip over cache files or the like. 0079 // NB: includes .so* and .py* since we also implicitly support snakes to 0080 // a degree 0081 // As a result we need to explicitly look for the main executable. 0082 if (pathname == exePath + deletedMarker) { 0083 qCWarning(DRKONQI_LOG) << "Found deleted exe marker" << pathname; 0084 return true; 0085 } 0086 0087 if (pathname != exePath && !isLibraryPath(QFile::decodeName(pathname))) { 0088 continue; // not exe and not a library. 0089 } 0090 0091 // Deleted marker always declares something missing. Even when we perform additional stat checks on it. 0092 if (pathname.endsWith(deletedMarker)) { 0093 qCWarning(DRKONQI_LOG) << "Found deleted library marker" << pathname; 0094 return true; 0095 } 0096 0097 switch (check) { 0098 case Check::DeletedMarker: { 0099 // If we get here the file hasn't been marked deleted. 0100 break; 0101 } 0102 case Check::Stat: { 0103 // Bit unfortunate this but when the file is on an overlayFS the entire stat validation system is unreliable. 0104 // The inode in the /maps is the actual underlying inode, not the st_ino we'll get when stating through 0105 // the overlay. 0106 if (QStorageInfo storageInfo(QString::fromUtf8(pathname)); storageInfo.fileSystemType().contains("overlay"_L1)) { 0107 qCDebug(DRKONQI_LOG) << "Couldn't perform stat check because file is on overlayfs" << pathname; 0108 break; 0109 } 0110 0111 struct stat info { 0112 }; 0113 const int ret = stat(pathname.constData(), &info); 0114 if (ret == -1) { 0115 qCWarning(DRKONQI_LOG) << "Couldn't stat file, assuming it was deleted" << pathname << strerror(errno); 0116 return true; 0117 break; 0118 } 0119 0120 if (info.st_ino != inode) { 0121 qCWarning(DRKONQI_LOG) << "Found mismatching inode on" << pathname << info.st_ino << inode; 0122 return true; 0123 break; 0124 } 0125 0126 // It's very awkward but st_dev seems dodgy at least with btrfs. The dev_t the kernel has is not the one 0127 // stat has and what's more the kernel has one that solid doesn't know about either. That may simply be 0128 // because btrfs makes up fake dev_ts since multiple btrfs subvolumes may be on the same block device. 0129 // Anyway, it's unfortunate but I guess we had best not look at the device. 0130 } break; 0131 } 0132 } 0133 0134 return false; 0135 }