208 lines
7.7 KiB
C++
208 lines
7.7 KiB
C++
|
/*
|
||
|
KWin - the KDE window manager
|
||
|
This file is part of the KDE project.
|
||
|
|
||
|
SPDX-FileCopyrightText: 2023 David Edmundson <davidedmundson@kde.org>
|
||
|
|
||
|
SPDX-License-Identifier: GPL-2.0-or-later
|
||
|
*/
|
||
|
#include "kwin_wayland_test.h"
|
||
|
|
||
|
#include "wayland/clientconnection.h"
|
||
|
#include "wayland/display.h"
|
||
|
#include "wayland_server.h"
|
||
|
|
||
|
#include <QTemporaryFile>
|
||
|
|
||
|
#include "KWayland/Client/connection_thread.h"
|
||
|
#include "KWayland/Client/registry.h"
|
||
|
|
||
|
#include <sys/socket.h>
|
||
|
#include <sys/un.h>
|
||
|
#include <unistd.h>
|
||
|
|
||
|
namespace KWin
|
||
|
{
|
||
|
|
||
|
static const QString s_socketName = QStringLiteral("wayland_test_security_context-0");
|
||
|
|
||
|
class SecurityContextTest : public QObject
|
||
|
{
|
||
|
Q_OBJECT
|
||
|
|
||
|
private Q_SLOTS:
|
||
|
void initTestCase();
|
||
|
void init();
|
||
|
void cleanup();
|
||
|
void testSecurityContext();
|
||
|
void testClosedCloseFdOnStartup();
|
||
|
};
|
||
|
|
||
|
void SecurityContextTest::initTestCase()
|
||
|
{
|
||
|
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
||
|
QVERIFY(waylandServer()->init(s_socketName));
|
||
|
kwinApp()->start();
|
||
|
QVERIFY(applicationStartedSpy.wait());
|
||
|
}
|
||
|
|
||
|
void SecurityContextTest::init()
|
||
|
{
|
||
|
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::SecurityContextManagerV1));
|
||
|
}
|
||
|
|
||
|
void SecurityContextTest::cleanup()
|
||
|
{
|
||
|
Test::destroyWaylandConnection();
|
||
|
}
|
||
|
|
||
|
void SecurityContextTest::testSecurityContext()
|
||
|
{
|
||
|
// This tests a mock flatpak server creating a Security Context
|
||
|
// connecting a client to the newly created server
|
||
|
// and making sure everything is torn down after the closeFd is closed
|
||
|
auto securityContextManager = Test::waylandSecurityContextManagerV1();
|
||
|
QVERIFY(securityContextManager);
|
||
|
|
||
|
int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
QVERIFY(listenFd != 0);
|
||
|
|
||
|
QTemporaryDir tempDir;
|
||
|
|
||
|
sockaddr_un sockaddr;
|
||
|
sockaddr.sun_family = AF_UNIX;
|
||
|
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", tempDir.filePath("socket").toUtf8().constData());
|
||
|
qDebug() << "listening socket:" << sockaddr.sun_path;
|
||
|
QVERIFY(bind(listenFd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == 0);
|
||
|
QVERIFY(listen(listenFd, 0) == 0);
|
||
|
|
||
|
int syncFds[2];
|
||
|
QVERIFY(pipe(syncFds) >= 0);
|
||
|
int closeFdForClientToKeep = syncFds[0];
|
||
|
int closeFdToGiveToKwin = syncFds[1];
|
||
|
|
||
|
auto securityContext = new QtWayland::wp_security_context_v1(securityContextManager->create_listener(listenFd, closeFdToGiveToKwin));
|
||
|
close(closeFdToGiveToKwin);
|
||
|
close(listenFd);
|
||
|
securityContext->set_instance_id("kde.unitest.instance_id");
|
||
|
securityContext->set_app_id("kde.unittest.app_id");
|
||
|
securityContext->set_sandbox_engine("test_sandbox_engine");
|
||
|
securityContext->commit();
|
||
|
securityContext->destroy();
|
||
|
delete securityContext;
|
||
|
|
||
|
qputenv("WAYLAND_DISPLAY", tempDir.filePath("socket").toUtf8());
|
||
|
QSignalSpy clientConnectedspy(waylandServer()->display(), &Display::clientConnected);
|
||
|
|
||
|
// connect a client using the newly created listening socket
|
||
|
KWayland::Client::ConnectionThread restrictedClientConnection;
|
||
|
QSignalSpy connectedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::connected);
|
||
|
QThread restictedClientThread;
|
||
|
auto restictedClientThreadQuitter = qScopeGuard([&restictedClientThread]() {
|
||
|
restictedClientThread.quit();
|
||
|
restictedClientThread.wait();
|
||
|
});
|
||
|
restrictedClientConnection.moveToThread(&restictedClientThread);
|
||
|
restictedClientThread.start();
|
||
|
restrictedClientConnection.initConnection();
|
||
|
QVERIFY(connectedSpy.wait());
|
||
|
|
||
|
// verify that our new restricted client is seen by kwin with the right security context
|
||
|
QVERIFY(clientConnectedspy.count());
|
||
|
QCOMPARE(clientConnectedspy.first().first().value<KWin::ClientConnection *>()->securityContextAppId(), "kde.unittest.app_id");
|
||
|
|
||
|
// verify that the globals for the restricted client does not contain the security context
|
||
|
KWayland::Client::Registry registry;
|
||
|
registry.create(&restrictedClientConnection);
|
||
|
QSignalSpy interfaceAnnounced(®istry, &KWayland::Client::Registry::interfaceAnnounced);
|
||
|
QSignalSpy allAnnouncedSpy(®istry, &KWayland::Client::Registry::interfacesAnnounced);
|
||
|
registry.setup();
|
||
|
QVERIFY(allAnnouncedSpy.wait());
|
||
|
for (auto interfaceSignal : interfaceAnnounced) {
|
||
|
QVERIFY(interfaceSignal.first().toString() != "wp_security_context_manager_v1");
|
||
|
}
|
||
|
|
||
|
// close the mock flatpak closeFDs
|
||
|
close(closeFdForClientToKeep);
|
||
|
|
||
|
// security context properties should have not changed after close-fd is closed
|
||
|
QVERIFY(Test::waylandSync());
|
||
|
QCOMPARE(clientConnectedspy.first().first().value<KWin::ClientConnection *>()->securityContextAppId(), "kde.unittest.app_id");
|
||
|
|
||
|
// new clients can't connect anymore
|
||
|
KWayland::Client::ConnectionThread restrictedClientConnection2;
|
||
|
QSignalSpy connectedSpy2(&restrictedClientConnection2, &KWayland::Client::ConnectionThread::connected);
|
||
|
QSignalSpy failedSpy2(&restrictedClientConnection2, &KWayland::Client::ConnectionThread::failed);
|
||
|
QThread restictedClientThread2;
|
||
|
auto restictedClientThreadQuitter2 = qScopeGuard([&restictedClientThread2]() {
|
||
|
restictedClientThread2.quit();
|
||
|
restictedClientThread2.wait();
|
||
|
});
|
||
|
restrictedClientConnection2.moveToThread(&restictedClientThread2);
|
||
|
restictedClientThread2.start();
|
||
|
restrictedClientConnection2.initConnection();
|
||
|
QVERIFY(failedSpy2.wait());
|
||
|
QVERIFY(connectedSpy2.isEmpty());
|
||
|
}
|
||
|
|
||
|
void SecurityContextTest::testClosedCloseFdOnStartup()
|
||
|
{
|
||
|
// This tests what would happen if the closeFd is already closed when kwin processes the security context
|
||
|
auto securityContextManager = Test::waylandSecurityContextManagerV1();
|
||
|
QVERIFY(securityContextManager);
|
||
|
|
||
|
int listenFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
|
QVERIFY(listenFd != 0);
|
||
|
|
||
|
QTemporaryDir tempDir;
|
||
|
|
||
|
sockaddr_un sockaddr;
|
||
|
sockaddr.sun_family = AF_UNIX;
|
||
|
snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", tempDir.filePath("socket").toUtf8().constData());
|
||
|
qDebug() << "listening socket:" << sockaddr.sun_path;
|
||
|
QVERIFY(bind(listenFd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) == 0);
|
||
|
QVERIFY(listen(listenFd, 0) == 0);
|
||
|
|
||
|
int syncFds[2];
|
||
|
QVERIFY(pipe(syncFds) >= 0);
|
||
|
int closeFdForClientToKeep = syncFds[0];
|
||
|
int closeFdToGiveToKwin = syncFds[1];
|
||
|
|
||
|
close(closeFdForClientToKeep); // closes the connection
|
||
|
|
||
|
auto securityContext = new QtWayland::wp_security_context_v1(securityContextManager->create_listener(listenFd, closeFdToGiveToKwin));
|
||
|
close(closeFdToGiveToKwin);
|
||
|
close(listenFd);
|
||
|
securityContext->set_instance_id("kde.unitest.instance_id");
|
||
|
securityContext->set_app_id("kde.unittest.app_id");
|
||
|
securityContext->set_sandbox_engine("test_sandbox_engine");
|
||
|
securityContext->commit();
|
||
|
securityContext->destroy();
|
||
|
delete securityContext;
|
||
|
|
||
|
QVERIFY(Test::waylandSync());
|
||
|
|
||
|
qputenv("WAYLAND_DISPLAY", tempDir.filePath("socket").toUtf8());
|
||
|
QSignalSpy clientConnectedspy(waylandServer()->display(), &Display::clientConnected);
|
||
|
|
||
|
// new clients can't connect anymore
|
||
|
KWayland::Client::ConnectionThread restrictedClientConnection;
|
||
|
QSignalSpy connectedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::connected);
|
||
|
QSignalSpy failedSpy(&restrictedClientConnection, &KWayland::Client::ConnectionThread::failed);
|
||
|
QThread restictedClientThread;
|
||
|
auto restictedClientThreadQuitter = qScopeGuard([&restictedClientThread]() {
|
||
|
restictedClientThread.quit();
|
||
|
restictedClientThread.wait();
|
||
|
});
|
||
|
restrictedClientConnection.moveToThread(&restictedClientThread);
|
||
|
restictedClientThread.start();
|
||
|
restrictedClientConnection.initConnection();
|
||
|
QVERIFY(failedSpy.wait());
|
||
|
QVERIFY(connectedSpy.isEmpty());
|
||
|
QVERIFY(clientConnectedspy.isEmpty());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
WAYLANDTEST_MAIN(KWin::SecurityContextTest)
|
||
|
#include "security_context_test.moc"
|