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