File indexing completed on 2024-05-12 05:30:41

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005  SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
0006 
0007  SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 #include "kwin_wayland_test.h"
0010 
0011 #include "wayland/clientconnection.h"
0012 #include "wayland/display.h"
0013 #include "wayland_server.h"
0014 
0015 #include <QTemporaryFile>
0016 
0017 #include "KWayland/Client/connection_thread.h"
0018 #include "KWayland/Client/registry.h"
0019 
0020 #include <sys/socket.h>
0021 #include <sys/un.h>
0022 #include <unistd.h>
0023 
0024 namespace KWin
0025 {
0026 
0027 static const QString s_socketName = QStringLiteral("wayland_test_security_context-0");
0028 
0029 class SecurityContextTest : public QObject
0030 {
0031     Q_OBJECT
0032 
0033 private Q_SLOTS:
0034     void initTestCase();
0035     void init();
0036     void cleanup();
0037     void testSecurityContext();
0038     void testClosedCloseFdOnStartup();
0039 };
0040 
0041 void SecurityContextTest::initTestCase()
0042 {
0043     QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
0044     QVERIFY(waylandServer()->init(s_socketName));
0045     kwinApp()->start();
0046     QVERIFY(applicationStartedSpy.wait());
0047 }
0048 
0049 void SecurityContextTest::init()
0050 {
0051     QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::SecurityContextManagerV1));
0052 }
0053 
0054 void SecurityContextTest::cleanup()
0055 {
0056     Test::destroyWaylandConnection();
0057 }
0058 
0059 void SecurityContextTest::testSecurityContext()
0060 {
0061     // This tests a mock flatpak server creating a Security Context
0062     // connecting a client to the newly created server
0063     // and making sure everything is torn down after the closeFd is closed
0064     auto securityContextManager = Test::waylandSecurityContextManagerV1();
0065     QVERIFY(securityContextManager);
0066 
0067     int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
0068     QVERIFY(listenFd != 0);
0069 
0070     QTemporaryDir tempDir;
0071 
0072     sockaddr_un sockaddr;
0073     sockaddr.sun_family = AF_UNIX;
0074     snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", tempDir.filePath("socket").toUtf8().constData());
0075     qDebug() << "listening socket:" << sockaddr.sun_path;
0076     QVERIFY(bind(listenFd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == 0);
0077     QVERIFY(listen(listenFd, 0) == 0);
0078 
0079     int syncFds[2];
0080     QVERIFY(pipe(syncFds) >= 0);
0081     int closeFdForClientToKeep = syncFds[0];
0082     int closeFdToGiveToKwin = syncFds[1];
0083 
0084     auto securityContext = new QtWayland::wp_security_context_v1(securityContextManager->create_listener(listenFd, closeFdToGiveToKwin));
0085     close(closeFdToGiveToKwin);
0086     close(listenFd);
0087     securityContext->set_instance_id("kde.unitest.instance_id");
0088     securityContext->set_app_id("kde.unittest.app_id");
0089     securityContext->set_sandbox_engine("test_sandbox_engine");
0090     securityContext->commit();
0091     securityContext->destroy();
0092     delete securityContext;
0093 
0094     qputenv("WAYLAND_DISPLAY", tempDir.filePath("socket").toUtf8());
0095     QSignalSpy clientConnectedspy(waylandServer()->display(), &Display::clientConnected);
0096 
0097     // connect a client using the newly created listening socket
0098     KWayland::Client::ConnectionThread restrictedClientConnection;
0099     QSignalSpy connectedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::connected);
0100     QThread restictedClientThread;
0101     auto restictedClientThreadQuitter = qScopeGuard([&restictedClientThread]() {
0102         restictedClientThread.quit();
0103         restictedClientThread.wait();
0104     });
0105     restrictedClientConnection.moveToThread(&restictedClientThread);
0106     restictedClientThread.start();
0107     restrictedClientConnection.initConnection();
0108     QVERIFY(connectedSpy.wait());
0109 
0110     // verify that our new restricted client is seen by kwin with the right security context
0111     QVERIFY(clientConnectedspy.count());
0112     QCOMPARE(clientConnectedspy.first().first().value<KWin::ClientConnection *>()->securityContextAppId(), "kde.unittest.app_id");
0113 
0114     // verify that the globals for the restricted client does not contain the security context
0115     KWayland::Client::Registry registry;
0116     registry.create(&restrictedClientConnection);
0117     QSignalSpy interfaceAnnounced(&registry, &KWayland::Client::Registry::interfaceAnnounced);
0118     QSignalSpy allAnnouncedSpy(&registry, &KWayland::Client::Registry::interfacesAnnounced);
0119     registry.setup();
0120     QVERIFY(allAnnouncedSpy.wait());
0121     for (auto interfaceSignal : interfaceAnnounced) {
0122         QVERIFY(interfaceSignal.first().toString() != "wp_security_context_manager_v1");
0123     }
0124 
0125     // close the mock flatpak closeFDs
0126     close(closeFdForClientToKeep);
0127 
0128     // security context properties should have not changed after close-fd is closed
0129     QVERIFY(Test::waylandSync());
0130     QCOMPARE(clientConnectedspy.first().first().value<KWin::ClientConnection *>()->securityContextAppId(), "kde.unittest.app_id");
0131 
0132     // new clients can't connect anymore
0133     KWayland::Client::ConnectionThread restrictedClientConnection2;
0134     QSignalSpy connectedSpy2(&restrictedClientConnection2, &KWayland::Client::ConnectionThread::connected);
0135     QSignalSpy failedSpy2(&restrictedClientConnection2, &KWayland::Client::ConnectionThread::failed);
0136     QThread restictedClientThread2;
0137     auto restictedClientThreadQuitter2 = qScopeGuard([&restictedClientThread2]() {
0138         restictedClientThread2.quit();
0139         restictedClientThread2.wait();
0140     });
0141     restrictedClientConnection2.moveToThread(&restictedClientThread2);
0142     restictedClientThread2.start();
0143     restrictedClientConnection2.initConnection();
0144     QVERIFY(failedSpy2.wait());
0145     QVERIFY(connectedSpy2.isEmpty());
0146 }
0147 
0148 void SecurityContextTest::testClosedCloseFdOnStartup()
0149 {
0150     // This tests what would happen if the closeFd is already closed when kwin processes the security context
0151     auto securityContextManager = Test::waylandSecurityContextManagerV1();
0152     QVERIFY(securityContextManager);
0153 
0154     int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
0155     QVERIFY(listenFd != 0);
0156 
0157     QTemporaryDir tempDir;
0158 
0159     sockaddr_un sockaddr;
0160     sockaddr.sun_family = AF_UNIX;
0161     snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", tempDir.filePath("socket").toUtf8().constData());
0162     qDebug() << "listening socket:" << sockaddr.sun_path;
0163     QVERIFY(bind(listenFd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == 0);
0164     QVERIFY(listen(listenFd, 0) == 0);
0165 
0166     int syncFds[2];
0167     QVERIFY(pipe(syncFds) >= 0);
0168     int closeFdForClientToKeep = syncFds[0];
0169     int closeFdToGiveToKwin = syncFds[1];
0170 
0171     close(closeFdForClientToKeep); // closes the connection
0172 
0173     auto securityContext = new QtWayland::wp_security_context_v1(securityContextManager->create_listener(listenFd, closeFdToGiveToKwin));
0174     close(closeFdToGiveToKwin);
0175     close(listenFd);
0176     securityContext->set_instance_id("kde.unitest.instance_id");
0177     securityContext->set_app_id("kde.unittest.app_id");
0178     securityContext->set_sandbox_engine("test_sandbox_engine");
0179     securityContext->commit();
0180     securityContext->destroy();
0181     delete securityContext;
0182 
0183     QVERIFY(Test::waylandSync());
0184 
0185     qputenv("WAYLAND_DISPLAY", tempDir.filePath("socket").toUtf8());
0186     QSignalSpy clientConnectedspy(waylandServer()->display(), &Display::clientConnected);
0187 
0188     // new clients can't connect anymore
0189     KWayland::Client::ConnectionThread restrictedClientConnection;
0190     QSignalSpy connectedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::connected);
0191     QSignalSpy failedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::failed);
0192     QThread restictedClientThread;
0193     auto restictedClientThreadQuitter = qScopeGuard([&restictedClientThread]() {
0194         restictedClientThread.quit();
0195         restictedClientThread.wait();
0196     });
0197     restrictedClientConnection.moveToThread(&restictedClientThread);
0198     restictedClientThread.start();
0199     restrictedClientConnection.initConnection();
0200     QVERIFY(failedSpy.wait());
0201     QVERIFY(connectedSpy.isEmpty());
0202     QVERIFY(clientConnectedspy.isEmpty());
0203 }
0204 }
0205 
0206 WAYLANDTEST_MAIN(KWin::SecurityContextTest)
0207 #include "security_context_test.moc"