kwin/autotests/integration/security_context_test.cpp
David Edmundson 4f9531ad77 Support SecurityContextManagerV1
This allows KWin to securely identify the client for a given connection,
without relying on the process name.

This patch does not do anything meaningful with the application ID other
than store it.

This first version does not support kwin restarts, it can come
afterwards.

Testing done:
With latest flatpak, running `WAYLAND_DEBUG=1 flatpak run org.telegram.desktop |& grep security`
shows that flatpak itself bound the security context, and the client did not see it advertised.
2023-11-13 09:51:28 +00:00

207 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(&registry, &KWayland::Client::Registry::interfaceAnnounced);
QSignalSpy allAnnouncedSpy(&registry, &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"