Commit 5e3c7b33 authored by Martin Flöser's avatar Martin Flöser

Use seccomp for implementing a sandbox for kscreenlocker_greet

Summary:
This change introduces a new optional dependency on libseccomp.
Libseccomp allows to forbid syscalls. With that we can constrain the
user defined dynamically loaded QtQuick code from the look'n'feel
package and from the wallpaper package. The idea is to protect against
"malicious" packages the user manually installed.

With the installed seccomp filter we can ensure that the QtQuick code
cannot perform the following operations:
* send password into Internet through forbidding the socket syscall
* use KIO to send password into Internet through forbidding fork+exec
* write password into a file through forbidding opening a file in
 write mode or creating a new file
* send password to another process through forbidding pipe/pipe2

So far our QtQuick code was already constrained by disallowing network
access through injecting a QNetworkAccessManager which forbids internet
access. But this was easy to circumvent through e.g. KIO.

The seccomp filter cannot protect against a malicious process already
running on the system. The obvious way to get out of this sandbox is
DBus. DBus is allowed in the sandbox, thus it is possible for a malicious
look'n'feel package to communicate with a running malicious application
through DBus. To protect DBus we need to implement an additional apparmor
profile.

The seccomp filter gets only installed if the seccomp dependency is
available and kcheckpass is not setuid. This is ensured with a runtime
check. For kscreenlocker_greet the main change is that when seccomp is
enabled the delayed kcheckpass authentication method is used.

Test Plan:
Manual testing and a new auto test which verifies the
restricted conditions.

Reviewers: #plasma

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D5029
parent 07d38ba6
......@@ -79,6 +79,16 @@ set_package_properties(loginctl PROPERTIES
)
option(PAM_REQUIRED "Require building with PAM" ON)
find_package(Seccomp)
set_package_properties(Seccomp PROPERTIES
TYPE
OPTIONAL
PURPOSE
"Used for putting the look'n'feel package in the greeter into a sandbox."
)
set(HAVE_SECCOMP ${Seccomp_FOUND})
include(ConfigureChecks.cmake)
configure_file(config-workspace.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-workspace.h)
......
#=============================================================================
# Copyright 2017 Martin Gräßlin <mgraesslin@kde.org>
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# 1. Redistributions of source code must retain the copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#=============================================================================
if(CMAKE_VERSION VERSION_LESS 2.8.12)
message(FATAL_ERROR "CMake 2.8.12 is required by FindSeccomp.cmake")
endif()
if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12)
message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use FindSeccomp.cmake")
endif()
find_package(PkgConfig)
pkg_check_modules(PKG_Libseccomp QUIET libseccomp)
set(Seccomp_DEFINITIONS ${PKG_Libseccomp_CFLAGS_OTHER})
set(Seccomp_VERSION ${PKG_Libseccomp_VERSION})
find_path(Seccomp_INCLUDE_DIR
NAMES
seccomp.h
HINTS
${PKG_Libseccomp_INCLUDE_DIRS}
)
find_library(Seccomp_LIBRARY
NAMES
seccomp
HINTS
${PKG_Libseccomp_LIBRARY_DIRS}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Seccomp
FOUND_VAR
Seccomp_FOUND
REQUIRED_VARS
Seccomp_LIBRARY
Seccomp_INCLUDE_DIR
VERSION_VAR
Seccomp_VERSION
)
if (Seccomp_FOUND AND NOT TARGET Seccomp::Seccomp)
add_library(Seccomp::Seccomp UNKNOWN IMPORTED)
set_target_properties(Seccomp::Seccomp PROPERTIES
IMPORTED_LOCATION "${Seccomp_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${Seccomp_DEFINITIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${Seccomp_INCLUDE_DIR}"
)
endif()
mark_as_advanced(Seccomp_LIBRARY Seccomp_INCLUDE_DIR)
include(FeatureSummary)
set_package_properties(Seccomp PROPERTIES
URL "https://github.com/seccomp/libseccomp"
DESCRIPTION "The enhanced seccomp library."
)
......@@ -12,3 +12,4 @@
#cmakedefine01 HAVE_PR_SET_DUMPABLE
#cmakedefine01 HAVE_SYS_PROCCTL_H
#cmakedefine01 HAVE_PROC_TRACE_CTL
#cmakedefine01 HAVE_SECCOMP
......@@ -15,6 +15,10 @@ set(kscreenlocker_greet_SRCS
wallpaper_integration.cpp
)
if(HAVE_SECCOMP)
set(kscreenlocker_greet_SRCS ${kscreenlocker_greet_SRCS} seccomp_filter.cpp)
endif()
qt5_add_resources(kscreenlocker_greet_SRCS fallbacktheme.qrc)
kconfig_add_kcfg_files(kscreenlocker_greet_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/../kcfg/kscreensaversettings.kcfgc)
......@@ -40,6 +44,9 @@ target_link_libraries(kscreenlocker_greet
KF5::WaylandClient
Wayland::Client
)
if(HAVE_SECCOMP)
target_link_libraries(kscreenlocker_greet Qt5::DBus Seccomp::Seccomp)
endif()
install(TARGETS kscreenlocker_greet DESTINATION ${KDE_INSTALL_LIBEXECDIR})
......
......@@ -26,3 +26,18 @@ ecm_mark_as_test(authenticatorTest)
add_executable(killTest killtest.cpp)
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)
ecm_mark_as_test(seccompTest)
target_link_libraries(seccompTest
Qt5::Test
Qt5::Gui
Qt5::DBus
Qt5::Network
Seccomp::Seccomp
)
endif()
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include <config-kscreenlocker.h>
#include "../seccomp_filter.h"
#include <QtTest/QtTest>
#include <QTemporaryFile>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <unistd.h>
class SeccompTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void initTestCase();
void testCreateFile();
void testOpenFile();
void testStartProcess();
void testNetworkAccess_data();
void testNetworkAccess();
};
void SeccompTest::initTestCase()
{
ScreenLocker::SecComp::init();
}
void SeccompTest::testCreateFile()
{
QTemporaryFile file;
QVERIFY(!file.open());
}
void SeccompTest::testOpenFile()
{
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::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<QString>("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"
......@@ -23,6 +23,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "authenticator.h"
#include "noaccessnetworkaccessmanagerfactory.h"
#include "wallpaper_integration.h"
#include <config-kscreenlocker.h>
// KDE
#include <KAuthorized>
......@@ -67,6 +68,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
//
#include <xcb/xcb.h>
#if HAVE_SECCOMP
#include <sys/stat.h>
#include <unistd.h>
#endif
// this is usable to fake a "screensaver" installation for testing
// *must* be "0" for every public commit!
#define TEST_SCREENSAVER 0
......@@ -98,12 +104,12 @@ UnlockApp::UnlockApp(int &argc, char **argv)
, m_testing(false)
, m_ignoreRequests(false)
, m_immediateLock(false)
, m_authenticator(new Authenticator(AuthenticationMode::Direct, this))
, m_graceTime(0)
, m_noLock(false)
, m_defaultToSwitchUser(false)
, m_wallpaperIntegration(new WallpaperIntegration(this))
{
m_authenticator = createAuthenticator();
connect(m_authenticator, &Authenticator::succeeded, this, &QCoreApplication::quit);
initialize();
connect(this, &UnlockApp::screenAdded, this, &UnlockApp::desktopResized);
......@@ -131,6 +137,19 @@ UnlockApp::~UnlockApp()
}
}
Authenticator *UnlockApp::createAuthenticator()
{
#if HAVE_SECCOMP
struct stat buf;
stat(KCHECKPASS_BIN, &buf);
if (!(buf.st_mode & S_ISUID)) {
m_supportsSeccomp = true;
return new Authenticator(AuthenticationMode::Delayed, this);
}
#endif
return new Authenticator(AuthenticationMode::Direct, this);
}
void UnlockApp::initialize()
{
initializeWayland();
......
......@@ -67,6 +67,10 @@ public:
void updateCanSuspend(bool set);
void updateCanHibernate(bool set);
bool supportsSeccomp() const {
return m_supportsSeccomp;
}
public Q_SLOTS:
void desktopResized();
......@@ -86,6 +90,7 @@ private:
void initializeWayland();
void shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from);
void loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view);
Authenticator *createAuthenticator();
QString m_packageName;
QUrl m_mainQmlPath;
......@@ -112,6 +117,7 @@ private:
KWayland::Client::PlasmaShell *m_plasmaShell = nullptr;
WallpaperIntegration *m_wallpaperIntegration;
bool m_supportsSeccomp = false;
};
} // namespace
......
......@@ -36,6 +36,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <unistd.h>
#include <sys/procctl.h>
#endif
#if HAVE_SECCOMP
#include "seccomp_filter.h"
#endif
static void signalHandler(int signum)
{
......@@ -166,6 +169,14 @@ int main(int argc, char* argv[])
app.setKsldSocket(fd);
}
}
// init the sandbox
#if HAVE_SECCOMP
if (app.supportsSeccomp()) {
ScreenLocker::SecComp::init();
}
#endif
app.desktopResized();
// This allow ksmserver to know when the applicaion has actually finished setting itself up.
......
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#include "seccomp_filter.h"
#include <QDBusConnection>
#include <QOpenGLContext>
#include <seccomp.h>
#include <sys/socket.h>
#include <fcntl.h>
namespace ScreenLocker
{
namespace SecComp
{
void init()
{
// trigger OpenGL context creation
// we need this to ensure that all required files are opened for write
QOpenGLContext::supportsThreadedOpenGL();
// 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
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(open), 1, SCMP_A1(SCMP_CMP_MASKED_EQ, O_RDWR, O_RDWR));
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);
}
}
}
/********************************************************************
KSld - the KDE Screenlocker Daemon
This file is part of the KDE project.
Copyright (C) 2017 Martin Gräßlin <mgraesslin@kde.org>
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 <http://www.gnu.org/licenses/>.
*********************************************************************/
#ifndef SCREENLOCKER_SECCOMP_FILTER_H
#define SCREENLOCKER_SECCOMP_FILTER_H
namespace ScreenLocker
{
namespace SecComp
{
void init();
}
}
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment