diff --git a/greeter/autotests/CMakeLists.txt b/greeter/autotests/CMakeLists.txt index d44f308..d5150e4 100644 --- a/greeter/autotests/CMakeLists.txt +++ b/greeter/autotests/CMakeLists.txt @@ -1,45 +1,46 @@ add_definitions(-DKSCREENLOCKER_UNIT_TEST) include(ECMMarkAsTest) ##################################### # fakekcheckpass ##################################### add_executable(fakekcheckpass fakekcheckpass.c) ecm_mark_nongui_executable(fakekcheckpass) target_link_libraries(fakekcheckpass ${SOCKET_LIBRARIES}) ####################################### # AuthenticatorTest ####################################### set( authenticatorTest_SRCS authenticatortest.cpp ../authenticator.cpp ) add_executable(authenticatorTest ${authenticatorTest_SRCS}) target_link_libraries(authenticatorTest Qt5::Test) add_test(ksmserver-authenticatorTest authenticatorTest) ecm_mark_as_test(authenticatorTest) ####################################### # KillTest ####################################### add_executable(killTest killtest.cpp) add_test(kscreenlocker-killTest killTest) ecm_mark_as_test(killTest) target_link_libraries(killTest Qt5::Test) ####################################### # Seccomp Test ####################################### if(HAVE_SECCOMP) add_executable(seccompTest seccomp_test.cpp ../seccomp_filter.cpp ../kwinglplatform.cpp) add_test(kscreenlocker-seccompTest seccompTest) ecm_mark_as_test(seccompTest) target_link_libraries(seccompTest Qt5::Test Qt5::Gui Qt5::DBus Qt5::Network + KF5::WindowSystem Seccomp::Seccomp ) endif() diff --git a/greeter/autotests/seccomp_test.cpp b/greeter/autotests/seccomp_test.cpp index 752efac..c650edb 100644 --- a/greeter/autotests/seccomp_test.cpp +++ b/greeter/autotests/seccomp_test.cpp @@ -1,145 +1,151 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2017 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include #include "../seccomp_filter.h" #include "../kwinglplatform.h" +#include + #include #include #include #include #include #include #ifdef __linux__ #include #endif #include #include #include #include class SeccompTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCreateFile(); void testOpenFile(); void testOpenFilePosix(); void testWriteFilePosix(); void testStartProcess(); void testNetworkAccess_data(); void testNetworkAccess(); }; void SeccompTest::initTestCase() { ScreenLocker::SecComp::init(); } void SeccompTest::testCreateFile() { + if (KWin::GLPlatform::instance()->isSoftwareEmulation() && KWindowSystem::isPlatformWayland()) { + QSKIP("File creation protection not supported with Mesa on Wayland"); + } + QTemporaryFile file; QVERIFY(!file.open()); } void SeccompTest::testOpenFile() { if (KWin::GLPlatform::instance()->driver() == KWin::Driver_NVidia) { QSKIP("Write protection not supported on NVIDIA"); } QFile file(QStringLiteral(KCHECKPASS_BIN)); QVERIFY(file.exists()); QVERIFY(!file.open(QIODevice::WriteOnly)); QVERIFY(!file.open(QIODevice::ReadWrite)); QVERIFY(file.open(QIODevice::ReadOnly)); } void SeccompTest::testOpenFilePosix() { QVERIFY(open("/dev/null", O_RDONLY | O_CREAT, 0) == -1 && errno == EPERM); QVERIFY(openat(AT_FDCWD, "/dev/null", O_RDONLY | O_CREAT, 0) == -1 && errno == EPERM); #ifdef SYS_open QVERIFY(syscall(SYS_open, "/dev/null", O_RDONLY | O_CREAT, 0) == -1 && errno == EPERM); #endif #ifdef SYS_openat QVERIFY(syscall(SYS_openat, AT_FDCWD, "/dev/null", O_RDONLY | O_CREAT, 0) == -1 && errno == EPERM); #endif } void SeccompTest::testWriteFilePosix() { if (KWin::GLPlatform::instance()->driver() == KWin::Driver_NVidia) { QSKIP("Write protection not supported on NVIDIA"); } QVERIFY(open("/dev/null", O_RDWR) == -1 && errno == EPERM); QVERIFY(openat(AT_FDCWD, "/dev/null", O_RDWR) == -1 && errno == EPERM); #ifdef SYS_open QVERIFY(syscall(SYS_open, "/dev/null", O_RDWR) == -1 && errno == EPERM); #endif #ifdef SYS_openat QVERIFY(syscall(SYS_openat, AT_FDCWD, "/dev/null", O_RDWR) == -1 && errno == EPERM); #endif } void SeccompTest::testStartProcess() { // QProcess fails already using pipe QProcess p; p.start(QStringLiteral(KCHECKPASS_BIN)); QVERIFY(!p.waitForStarted()); QCOMPARE(p.error(), QProcess::ProcessError::FailedToStart); // using glibc fork succeeds as it uses clone // we don't forbid clone as it's needed to start a new thread // so only test that exec fails QCOMPARE(execl(KCHECKPASS_BIN, "fakekcheckpass", (char*)0), -1); QCOMPARE(errno, EPERM); } void SeccompTest::testNetworkAccess_data() { QTest::addColumn("url"); // TODO: maybe resolve the IP addresses prior to installing seccomp? QTest::newRow("domain") << QStringLiteral("https://www.kde.org"); QTest::newRow("ip4") << QStringLiteral("http://91.189.93.5"); // phabricator.kde.org QTest::newRow("ip6") << QStringLiteral("http://[2a01:4f8:171:2687::4]/"); } void SeccompTest::testNetworkAccess() { QNetworkAccessManager manager; QFETCH(QString, url); QNetworkRequest request(url); auto reply = manager.get(request); QVERIFY(reply); QSignalSpy finishedSpy(reply, &QNetworkReply::finished); QVERIFY(finishedSpy.isValid()); QVERIFY(finishedSpy.wait()); QVERIFY(reply->error() != QNetworkReply::NoError); } QTEST_MAIN(SeccompTest) #include "seccomp_test.moc" diff --git a/greeter/seccomp_filter.cpp b/greeter/seccomp_filter.cpp index f0526ae..029500f 100644 --- a/greeter/seccomp_filter.cpp +++ b/greeter/seccomp_filter.cpp @@ -1,106 +1,115 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2017 Martin Gräßlin This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License or (at your option) version 3 or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of version 3 of the license. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *********************************************************************/ #include "seccomp_filter.h" #include "kwinglplatform.h" +#include + #include #include #include #include #include #include namespace ScreenLocker { namespace SecComp { void init() { // trigger OpenGL context creation // we need this to ensure that all required files are opened for write // on NVIDIA we need to keep write around, otherwise BUG 384005 happens bool writeSupported = true; + // Mesa's software renderers create buffers in $XDG_RUNTIME_DIR on wayland + bool createSupported = true; QScopedPointer dummySurface(new QOffscreenSurface); dummySurface->create(); QOpenGLContext dummyGlContext; if (dummyGlContext.create()) { if (dummyGlContext.makeCurrent(dummySurface.data())) { auto gl = KWin::GLPlatform::instance(); gl->detect(); gl->printResults(); if (gl->driver() == KWin::Driver_NVidia) { // BUG: 384005 writeSupported = false; } + else if (gl->isSoftwareEmulation() && KWindowSystem::isPlatformWayland()) { + createSupported = writeSupported = false; + } } } // access DBus to have the socket open QDBusConnection::sessionBus(); // default action: allow // we cannot use a whitelist approach of syscalls // Qt, OpenGL, DBus just need to much and too broad auto context = seccomp_init(SCMP_ACT_ALLOW); if (!context) { return; } // add a filter to prevent that the password gets written to a file // we cannot disallow write syscall. That one is needed to wake up threads // Qt and OpenGL might create additional threads and then it would fail as we have an fd which // is not allowed to write to // instead disallow opening new files for writing // they should fail with EPERM error if (writeSupported) { seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(openat), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, O_WRONLY, O_WRONLY)); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(openat), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR)); } - seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(openat), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT)); - seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT)); + if (createSupported) { + seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(openat), 1, SCMP_A2(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT)); + seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_CREAT, O_CREAT)); + } // disallow going to a socket seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(socket), 0); // disallow fork+exec // note glibc seems to use clone which is allowed for threads seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(fork), 0); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(vfork), 0); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execve), 0); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(execveat), 0); // disallow pipe, that should destroy copy and paste on Wayland seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(pipe), 0); seccomp_rule_add(context, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(pipe2), 0); // and activate our rules seccomp_load(context); seccomp_release(context); } } }