File indexing completed on 2024-04-21 16:12:22

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