File indexing completed on 2024-11-10 04:57:38

0001 /*
0002     SPDX-FileCopyrightText: 2021 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "xwaylandsocket.h"
0008 #include "xwayland_logging.h"
0009 
0010 #include <QCoreApplication>
0011 #include <QFile>
0012 #include <QScopeGuard>
0013 
0014 #include <errno.h>
0015 #include <signal.h>
0016 #include <sys/socket.h>
0017 #include <sys/stat.h>
0018 #include <sys/un.h>
0019 #include <unistd.h>
0020 
0021 namespace KWin
0022 {
0023 
0024 class UnixSocketAddress
0025 {
0026 public:
0027     enum class Type {
0028         Unix,
0029         Abstract,
0030     };
0031 
0032     UnixSocketAddress(const QString &socketPath, Type type);
0033 
0034     const sockaddr *data() const;
0035     int size() const;
0036 
0037 private:
0038     QByteArray m_buffer;
0039 };
0040 
0041 UnixSocketAddress::UnixSocketAddress(const QString &socketPath, Type type)
0042 {
0043     const QByteArray encodedSocketPath = QFile::encodeName(socketPath);
0044 
0045     int byteCount = offsetof(sockaddr_un, sun_path) + encodedSocketPath.size() + 1;
0046     m_buffer.resize(byteCount);
0047 
0048     sockaddr_un *address = reinterpret_cast<sockaddr_un *>(m_buffer.data());
0049     address->sun_family = AF_UNIX;
0050 
0051     if (type == Type::Unix) {
0052         memcpy(address->sun_path, encodedSocketPath.data(), encodedSocketPath.size());
0053         address->sun_path[encodedSocketPath.size()] = '\0';
0054     } else {
0055         // Abstract domain socket does not need the NUL-termination byte.
0056         *address->sun_path = '\0';
0057         memcpy(address->sun_path + 1, encodedSocketPath.data(), encodedSocketPath.size());
0058     }
0059 }
0060 
0061 const sockaddr *UnixSocketAddress::data() const
0062 {
0063     return reinterpret_cast<const sockaddr *>(m_buffer.data());
0064 }
0065 
0066 int UnixSocketAddress::size() const
0067 {
0068     return m_buffer.size();
0069 }
0070 
0071 static QString lockFileNameForDisplay(int display)
0072 {
0073     return QStringLiteral("/tmp/.X%1-lock").arg(display);
0074 }
0075 
0076 static QString socketFileNameForDisplay(int display)
0077 {
0078     return QStringLiteral("/tmp/.X11-unix/X%1").arg(display);
0079 }
0080 
0081 static bool tryLockFile(const QString &lockFileName)
0082 {
0083     for (int attempt = 0; attempt < 3; ++attempt) {
0084         QFile lockFile(lockFileName);
0085         if (lockFile.open(QFile::WriteOnly | QFile::NewOnly)) {
0086             char buffer[12];
0087             snprintf(buffer, sizeof(buffer), "%10lld\n", QCoreApplication::applicationPid());
0088             if (lockFile.write(buffer, sizeof(buffer) - 1) != sizeof(buffer) - 1) {
0089                 qCWarning(KWIN_XWL) << "Failed to write pid to lock file:" << lockFile.errorString();
0090                 lockFile.remove();
0091                 return false;
0092             }
0093             return true;
0094         } else if (lockFile.open(QFile::ReadOnly)) {
0095             const int lockPid = lockFile.readLine().trimmed().toInt();
0096             if (!lockPid) {
0097                 return false;
0098             }
0099             if (kill(lockPid, 0) < 0 && errno == ESRCH) {
0100                 lockFile.remove(); // Try to grab the lock file in the next loop iteration.
0101             } else {
0102                 return false;
0103             }
0104         }
0105     }
0106     return false;
0107 }
0108 
0109 static int listen_helper(const QString &filePath, UnixSocketAddress::Type type, XwaylandSocket::OperationMode mode)
0110 {
0111     const UnixSocketAddress socketAddress(filePath, type);
0112 
0113     int socketFlags = SOCK_STREAM;
0114     if (mode == XwaylandSocket::OperationMode::CloseFdsOnExec) {
0115         socketFlags |= SOCK_CLOEXEC;
0116     }
0117     int fileDescriptor = socket(AF_UNIX, socketFlags, 0);
0118     if (fileDescriptor == -1) {
0119         return -1;
0120     }
0121 
0122     if (bind(fileDescriptor, socketAddress.data(), socketAddress.size()) == -1) {
0123         close(fileDescriptor);
0124         return -1;
0125     }
0126 
0127     if (listen(fileDescriptor, 1) == -1) {
0128         close(fileDescriptor);
0129         return -1;
0130     }
0131 
0132     return fileDescriptor;
0133 }
0134 
0135 static bool checkSocketsDirectory()
0136 {
0137     struct stat info;
0138     const char *path = "/tmp/.X11-unix";
0139 
0140     if (lstat(path, &info) != 0) {
0141         if (errno == ENOENT) {
0142             qCWarning(KWIN_XWL) << path << "does not exist. Please check your installation";
0143             return false;
0144         }
0145 
0146         qCWarning(KWIN_XWL, "Failed to stat %s: %s", path, strerror(errno));
0147         return false;
0148     }
0149 
0150     if (!S_ISDIR(info.st_mode)) {
0151         qCWarning(KWIN_XWL) << path << "is not a directory. Broken system?";
0152         return false;
0153     }
0154     if (info.st_uid != 0 && info.st_uid != getuid()) {
0155         qCWarning(KWIN_XWL) << path << "is not owned by root or us";
0156         return false;
0157     }
0158     if (!(info.st_mode & S_ISVTX)) {
0159         qCWarning(KWIN_XWL) << path << "has no sticky bit on. Your system might be compromised!";
0160         return false;
0161     }
0162 
0163     return true;
0164 }
0165 
0166 XwaylandSocket::XwaylandSocket(OperationMode mode)
0167 {
0168     if (!checkSocketsDirectory()) {
0169         return;
0170     }
0171 
0172     for (int display = 0; display < 100; ++display) {
0173         const QString socketFilePath = socketFileNameForDisplay(display);
0174         const QString lockFilePath = lockFileNameForDisplay(display);
0175 
0176         if (!tryLockFile(lockFilePath)) {
0177             continue;
0178         }
0179 
0180         QList<int> fileDescriptors;
0181         auto socketCleanup = qScopeGuard([&fileDescriptors]() {
0182             for (const int &fileDescriptor : std::as_const(fileDescriptors)) {
0183                 close(fileDescriptor);
0184             }
0185         });
0186 
0187         QFile::remove(socketFilePath);
0188         const int unixFileDescriptor = listen_helper(socketFilePath, UnixSocketAddress::Type::Unix, mode);
0189         if (unixFileDescriptor == -1) {
0190             QFile::remove(lockFilePath);
0191             continue;
0192         }
0193         fileDescriptors << unixFileDescriptor;
0194 
0195 #if defined(Q_OS_LINUX)
0196         const int abstractFileDescriptor = listen_helper(socketFilePath, UnixSocketAddress::Type::Abstract, mode);
0197         if (abstractFileDescriptor == -1) {
0198             QFile::remove(lockFilePath);
0199             QFile::remove(socketFilePath);
0200             continue;
0201         }
0202         fileDescriptors << abstractFileDescriptor;
0203 #endif
0204 
0205         m_fileDescriptors = fileDescriptors;
0206         socketCleanup.dismiss();
0207 
0208         m_socketFilePath = socketFilePath;
0209         m_lockFilePath = lockFilePath;
0210         m_display = display;
0211         return;
0212     }
0213 
0214     qCWarning(KWIN_XWL) << "Failed to find free X11 connection socket";
0215 }
0216 
0217 XwaylandSocket::~XwaylandSocket()
0218 {
0219     for (const int &fileDescriptor : std::as_const(m_fileDescriptors)) {
0220         close(fileDescriptor);
0221     }
0222     if (!m_socketFilePath.isEmpty()) {
0223         QFile::remove(m_socketFilePath);
0224     }
0225     if (!m_lockFilePath.isEmpty()) {
0226         QFile::remove(m_lockFilePath);
0227     }
0228 }
0229 
0230 bool XwaylandSocket::isValid() const
0231 {
0232     return m_display != -1;
0233 }
0234 
0235 QList<int> XwaylandSocket::fileDescriptors() const
0236 {
0237     return m_fileDescriptors;
0238 }
0239 
0240 int XwaylandSocket::display() const
0241 {
0242     return m_display;
0243 }
0244 
0245 QString XwaylandSocket::name() const
0246 {
0247     return ":" + QString::number(m_display);
0248 }
0249 
0250 } // namespace KWin