diff --git a/greeter/autotests/seccomp_test.cpp b/greeter/autotests/seccomp_test.cpp index 571c79d..752efac 100644 --- a/greeter/autotests/seccomp_test.cpp +++ b/greeter/autotests/seccomp_test.cpp @@ -1,110 +1,145 @@ /******************************************************************** 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 +#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() { 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 9d61d4a..f0526ae 100644 --- a/greeter/seccomp_filter.cpp +++ b/greeter/seccomp_filter.cpp @@ -1,103 +1,106 @@ /******************************************************************** 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 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; 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; } } } // 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)); // 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); } } }