diff --git a/CMakeLists.txt b/CMakeLists.txt index 3fdfb18..c7a41be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,221 +1,231 @@ set(PROJECT_VERSION "5.9.90") set(PROJECT_VERSION_MAJOR 5) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) set(QT_MIN_VERSION "5.4.0") set(KF5_MIN_VERSION "5.15.0") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Widgets Quick QuickWidgets Test) find_package(ECM 1.8.0 REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMSetupVersion) include(ECMGenerateHeaders) include(CheckIncludeFiles) include(ECMMarkNonGuiExecutable) include(ECMPackageConfigHelpers) include(FeatureSummary) include(GenerateExportHeader) include(CheckIncludeFile) include(CheckSymbolExists) check_include_file("sys/prctl.h" HAVE_SYS_PRCTL_H) check_symbol_exists(PR_SET_DUMPABLE "sys/prctl.h" HAVE_PR_SET_DUMPABLE) check_include_file("sys/procctl.h" HAVE_SYS_PROCCTL_H) check_symbol_exists(PROC_TRACE_CTL "sys/procctl.h" HAVE_PROC_TRACE_CTL) if (HAVE_PR_SET_DUMPABLE OR HAVE_PROC_TRACE_CTL) set(CAN_DISABLE_PTRACE TRUE) endif () add_feature_info("prctl/procctl tracing control" CAN_DISABLE_PTRACE "Required for disallowing ptrace on greeter and kcheckpass process") find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Crash Declarative GlobalAccel I18n IdleTime KCMUtils Notifications Solid TextWidgets WindowSystem XmlGui ) find_package(X11) set_package_properties(X11 PROPERTIES DESCRIPTION "X11 libraries" URL "http://www.x.org" TYPE REQUIRED PURPOSE "Required for building the X11 based workspace") find_package(XCB MODULE REQUIRED COMPONENTS XCB KEYSYMS XTEST) set_package_properties(XCB PROPERTIES TYPE REQUIRED) add_feature_info("XInput" X11_Xinput_FOUND "Required for grabbing XInput2 devices in the screen locker") find_package(Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS X11Extras) find_package(KF5Wayland CONFIG REQUIRED) set_package_properties(KF5Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building screenlocker") find_package(WaylandScanner) find_package(Wayland 1.3 COMPONENTS Client Server) set_package_properties(Wayland PROPERTIES TYPE REQUIRED PURPOSE "Required for building screenlocker") find_package(loginctl) set_package_properties(loginctl PROPERTIES URL "https://www.freedesktop.org/software/systemd/man/loginctl.html" DESCRIPTION "Send control commands to the login manager" TYPE RUNTIME PURPOSE "Needed for emergency unlock in case that the greeter is broken. In case your distribution does not provide loginctl please contact plasma-devel@kde.org to discuss alternatives." ) 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) configure_file(config-unix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-unix.h ) configure_file(config-X11.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-X11.h) # adjusting CMAKE_C_FLAGS to get wayland protocols to compile set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu90") ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KSCREENLOCKER VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kscreenlocker_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfigVersion.cmake" SOVERSION 5) configure_file(config-kscreenlocker.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kscreenlocker.h) include_directories(${CMAKE_CURRENT_BINARY_DIR}) set(KSLD_INCLUDEDIR "${CMAKE_INSTALL_INCLUDEDIR}/KScreenLocker") add_subdirectory(kcheckpass) add_subdirectory(greeter) add_subdirectory(kcm) add_definitions(-DTRANSLATION_DOMAIN=\"kscreenlocker\") set(screensaver_dbusXML dbus/org.freedesktop.ScreenSaver.xml) set(kscreensaver_dbusXML dbus/org.kde.screensaver.xml) set(powerdevilpolicyagent_xml dbus/kf5_org.kde.Solid.PowerManagement.PolicyAgent.xml) set(ksld_SRCS abstractlocker.cpp ksldapp.cpp interface.cpp globalaccel.cpp x11locker.cpp waylandlocker.cpp logind.cpp waylandserver.cpp powermanagement.cpp powermanagement_inhibition.cpp ) qt5_add_dbus_adaptor(ksld_SRCS ${screensaver_dbusXML} interface.h ScreenLocker::Interface) qt5_add_dbus_adaptor(ksld_SRCS ${kscreensaver_dbusXML} interface.h ScreenLocker::Interface kscreensaveradaptor KScreenSaverAdaptor) kconfig_add_kcfg_files(ksld_SRCS kcfg/kscreensaversettings.kcfgc) qt5_add_dbus_interface(ksld_SRCS ${powerdevilpolicyagent_xml} powerdevilpolicyagent) ecm_add_wayland_server_protocol(ksld_SRCS PROTOCOL protocols/ksld.xml BASENAME ksld ) add_library(KScreenLocker SHARED ${ksld_SRCS}) target_link_libraries(KScreenLocker PUBLIC Qt5::Core Qt5::X11Extras PRIVATE KF5::I18n KF5::IdleTime KF5::GlobalAccel KF5::Notifications KF5::CoreAddons KF5::ConfigGui KF5::WindowSystem ${X11_LIBRARIES} XCB::XCB XCB::KEYSYMS KF5::WaylandServer Wayland::Server ) if (X11_Xinput_FOUND) target_link_libraries(KScreenLocker PRIVATE ${X11_Xinput_LIB}) endif() target_include_directories(KScreenLocker INTERFACE "$") # Needed to compile on Arm target. set_target_properties(KScreenLocker PROPERTIES COMPILE_FLAGS "-fPIC") add_library(PW::KScreenLocker ALIAS KScreenLocker) generate_export_header(KScreenLocker BASE_NAME KScreenLocker EXPORT_MACRO_NAME KSCREENLOCKER_EXPORT EXPORT_FILE_NAME KScreenLocker/kscreenlocker_export.h ) ecm_configure_package_config_file(ScreenSaverDBusInterfaceConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/ScreenSaverDBusInterfaceConfig.cmake PATH_VARS KDE_INSTALL_DBUSINTERFACEDIR INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/ScreenSaverDBusInterface") set_target_properties(KScreenLocker PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/ScreenSaverDBusInterfaceConfig.cmake DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/ScreenSaverDBusInterface") ecm_configure_package_config_file("${CMAKE_CURRENT_SOURCE_DIR}/KScreenLockerConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker") install(TARGETS KScreenLocker EXPORT KScreenLockerTargets ${INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY) install(EXPORT KScreenLockerTargets DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker" FILE KScreenLockerTargets.cmake NAMESPACE PW::) ecm_generate_headers(KScreenLocker_CamelCase_HEADERS HEADER_NAMES KsldApp REQUIRED_HEADERS KScreenLocker_HEADERS) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KScreenLocker/kscreenlocker_export.h ${KScreenLocker_CamelCase_HEADERS} ${KScreenLocker_HEADERS} DESTINATION ${KSLD_INCLUDEDIR}/KScreenLocker COMPONENT Devel) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KScreenLockerConfigVersion.cmake" DESTINATION "${CMAKECONFIG_INSTALL_PREFIX}/KScreenLocker" COMPONENT Devel) install(FILES kscreenlocker.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFY5RCDIR} RENAME ksmserver.notifyrc) install(FILES ${screensaver_dbusXML} DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.freedesktop.ScreenSaver.xml) install(FILES updaters/kscreenlocker.upd DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update) install(PROGRAMS updaters/ksreenlocker_5_3_separate_autologin.pl DESTINATION ${KDE_INSTALL_DATADIR}/kconf_update) add_subdirectory(autotests) add_subdirectory(tests) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/cmake/FindSeccomp.cmake b/cmake/FindSeccomp.cmake new file mode 100644 index 0000000..f641c25 --- /dev/null +++ b/cmake/FindSeccomp.cmake @@ -0,0 +1,80 @@ +#============================================================================= +# Copyright 2017 Martin Gräßlin +# +# 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." +) diff --git a/config-kscreenlocker.h.cmake b/config-kscreenlocker.h.cmake index c30e6b1..276e272 100644 --- a/config-kscreenlocker.h.cmake +++ b/config-kscreenlocker.h.cmake @@ -1,14 +1,15 @@ #ifdef KSCREENLOCKER_TEST_APPS #define KCHECKPASS_BIN "${CMAKE_BINARY_DIR}/kcheckpass/kcheckpass" #elif defined(KSCREENLOCKER_UNIT_TEST) #define KCHECKPASS_BIN "${CMAKE_CURRENT_BINARY_DIR}/greeter/autotests/fakekcheckpass" #else #define KCHECKPASS_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kcheckpass" #endif #define KSCREENLOCKER_GREET_BIN "${CMAKE_INSTALL_FULL_LIBEXECDIR}/kscreenlocker_greet" #cmakedefine01 HAVE_SYS_PRCTL_H #cmakedefine01 HAVE_PR_SET_DUMPABLE #cmakedefine01 HAVE_SYS_PROCCTL_H #cmakedefine01 HAVE_PROC_TRACE_CTL +#cmakedefine01 HAVE_SECCOMP diff --git a/greeter/CMakeLists.txt b/greeter/CMakeLists.txt index 0267eae..c5b639a 100644 --- a/greeter/CMakeLists.txt +++ b/greeter/CMakeLists.txt @@ -1,48 +1,55 @@ remove_definitions(-DTRANSLATION_DOMAIN=\"kscreenlocker\") add_definitions(-DTRANSLATION_DOMAIN=\"kscreenlocker_greet\") include_directories( ${CMAKE_CURRENT_BINARY_DIR} ../kcheckpass ${CMAKE_CURRENT_BINARY_DIR}/../ ) set(kscreenlocker_greet_SRCS authenticator.cpp greeterapp.cpp main.cpp noaccessnetworkaccessmanagerfactory.cpp 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) ecm_add_wayland_client_protocol(kscreenlocker_greet_SRCS PROTOCOL ../protocols/ksld.xml BASENAME ksld ) add_executable(kscreenlocker_greet ${kscreenlocker_greet_SRCS}) target_link_libraries(kscreenlocker_greet KF5::Package KF5::Crash KF5::I18n KF5::ConfigGui KF5::Declarative KF5::QuickAddons KF5::WindowSystem Qt5::Quick Qt5::Qml Qt5::X11Extras ${X11_LIBRARIES} 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}) install(DIRECTORY themes/org.kde.passworddialog DESTINATION ${KDE_INSTALL_DATADIR}/ksmserver/screenlocker) add_subdirectory(autotests) diff --git a/greeter/autotests/CMakeLists.txt b/greeter/autotests/CMakeLists.txt index 73b32d0..5ef4e41 100644 --- a/greeter/autotests/CMakeLists.txt +++ b/greeter/autotests/CMakeLists.txt @@ -1,28 +1,43 @@ 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) 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() diff --git a/greeter/autotests/seccomp_test.cpp b/greeter/autotests/seccomp_test.cpp new file mode 100644 index 0000000..0006140 --- /dev/null +++ b/greeter/autotests/seccomp_test.cpp @@ -0,0 +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) 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 +#include +#include + +#include +#include +#include + +#include + +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("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/greeterapp.cpp b/greeter/greeterapp.cpp index 8746b75..18d8e31 100644 --- a/greeter/greeterapp.cpp +++ b/greeter/greeterapp.cpp @@ -1,705 +1,724 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2004 Chris Howells Copyright (C) 2011 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 "greeterapp.h" #include "kscreensaversettings.h" #include "authenticator.h" #include "noaccessnetworkaccessmanagerfactory.h" #include "wallpaper_integration.h" +#include // KDE #include #include #include #include #include #include //Plasma #include #include #include // KWayland #include #include #include #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include // Wayland #include #include // X11 #include #include #include // #include +#if HAVE_SECCOMP +#include +#include +#endif + // this is usable to fake a "screensaver" installation for testing // *must* be "0" for every public commit! #define TEST_SCREENSAVER 0 namespace ScreenLocker { class FocusOutEventFilter : public QAbstractNativeEventFilter { public: bool nativeEventFilter(const QByteArray &eventType, void *message, long int *result) override { Q_UNUSED(result) if (qstrcmp(eventType, "xcb_generic_event_t") != 0) { return false; } xcb_generic_event_t *event = reinterpret_cast(message); if ((event->response_type & ~0x80) == XCB_FOCUS_OUT) { return true; } return false; } }; // App UnlockApp::UnlockApp(int &argc, char **argv) : QGuiApplication(argc, argv) , m_resetRequestIgnoreTimer(new QTimer(this)) , m_delayedLockTimer(0) , 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); connect(this, &UnlockApp::screenRemoved, this, &UnlockApp::desktopResized); if (QX11Info::isPlatformX11()) { installNativeEventFilter(new FocusOutEventFilter); } } UnlockApp::~UnlockApp() { qDeleteAll(m_views); if (m_ksldInterface) { org_kde_ksld_destroy(m_ksldInterface); } if (m_ksldRegistry) { delete m_ksldRegistry; } if (m_ksldConnection) { m_ksldConnection->deleteLater(); m_ksldConnectionThread->quit(); m_ksldConnectionThread->wait(); } } +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(); // set up the request ignore timeout, so that multiple requests to sleep/suspend/shutdown // are not processed in quick (and confusing) succession) m_resetRequestIgnoreTimer->setSingleShot(true); m_resetRequestIgnoreTimer->setInterval(2000); connect(m_resetRequestIgnoreTimer, &QTimer::timeout, this, &UnlockApp::resetRequestIgnore); // disable DrKonqi as the crash dialog blocks the restart of the locker KCrash::setDrKonqiEnabled(false); KScreenSaverSettings::self()->load(); KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE"); m_packageName = cg.readEntry("LookAndFeelPackage", QString()); if (!m_packageName.isEmpty()) { package.setPath(m_packageName); } if (!KScreenSaverSettings::theme().isEmpty()) { package.setPath(KScreenSaverSettings::theme()); } m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript")); m_wallpaperIntegration->setConfig(KScreenSaverSettings::self()->sharedConfig()); m_wallpaperIntegration->setPluginName(KScreenSaverSettings::self()->wallpaperPlugin()); m_wallpaperIntegration->init(); installEventFilter(this); } void UnlockApp::initializeWayland() { if (!platformName().startsWith(QLatin1String("wayland"), Qt::CaseInsensitive)) { return; } using namespace KWayland::Client; auto *c = ConnectionThread::fromApplication(this); if (!c) { return; } Registry *r = new Registry(this); r->create(c); r->setup(); c->roundtrip(); const auto i = r->interface(Registry::Interface::PlasmaShell); if (i.name == 0) { return; } m_plasmaShell = r->createPlasmaShell(i.name, i.version, this); } void UnlockApp::loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view) { auto package = m_wallpaperIntegration->package(); if (!package.isValid()) { qWarning() << "Error loading the wallpaper, no valid package loaded"; return; } auto qmlObject = new KDeclarative::QmlObjectSharedEngine(view); qmlObject->setInitializationDelayed(true); qmlObject->setPackage(package); qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), m_wallpaperIntegration); view->setProperty("wallpaperGraphicsObject", QVariant::fromValue(qmlObject)); connect(qmlObject, &KDeclarative::QmlObject::finished, this, [this, qmlObject, view] { auto item = qobject_cast(qmlObject->rootObject()); if (!item) { qWarning() << "Wallpaper needs to be a QtQuick Item"; return; } item->setParentItem(view->rootObject()); item->setZ(-1000); //set anchors QQmlExpression expr(qmlObject->engine()->rootContext(), item, QStringLiteral("parent")); QQmlProperty prop(item, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } ); } void UnlockApp::desktopResized() { const int nScreens = screens().count(); // remove useless views and savers while (m_views.count() > nScreens) { m_views.takeLast()->deleteLater(); } // extend views and savers to current demand for (int i = m_views.count(); i < nScreens; ++i) { // create the view auto *view = new KQuickAddons::QuickViewSharedEngine(); view->setColor(Qt::black); // first create KDeclarative, to be sure that it created a KIO Network Factory KDeclarative::KDeclarative declarative; declarative.setDeclarativeEngine(view->engine()); declarative.setupBindings(); if (!m_testing) { if (QX11Info::isPlatformX11()) { view->setFlags(Qt::X11BypassWindowManagerHint); } else { view->setFlags(Qt::FramelessWindowHint); } } if (m_ksldInterface) { view->create(); org_kde_ksld_x11window(m_ksldInterface, view->winId()); wl_display_flush(m_ksldConnection->display()); } if (m_plasmaShell) { using namespace KWayland::Client; if (Surface *surface = Surface::fromWindow(view)) { PlasmaShellSurface *shellSurface = m_plasmaShell->createSurface(surface, view); view->setProperty("plasmaShellSurface", QVariant::fromValue(shellSurface)); } } // engine stuff QQmlContext* context = view->engine()->rootContext(); const KUser user; const QString fullName = user.property(KUser::FullName).toString(); context->setContextProperty(QStringLiteral("kscreenlocker_userName"), fullName.isEmpty() ? user.loginName() : fullName); context->setContextProperty(QStringLiteral("kscreenlocker_userImage"), user.faceIconPath()); context->setContextProperty(QStringLiteral("authenticator"), m_authenticator); context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_interfaceVersion"), 2); context->setContextProperty(QStringLiteral("org_kde_plasma_screenlocker_greeter_view"), view); context->setContextProperty(QStringLiteral("defaultToSwitchUser"), m_defaultToSwitchUser); view->setSource(m_mainQmlPath); // on error, load the fallback lockscreen to not lock the user out of the system if (view->status() == QQmlComponent::Error) { static const QUrl fallbackUrl(QUrl(QStringLiteral("qrc:/fallbacktheme/LockScreen.qml"))); qWarning() << "Failed to load lockscreen QML, falling back to built-in locker"; m_mainQmlPath = fallbackUrl; view->setSource(fallbackUrl); } view->setResizeMode(KQuickAddons::QuickViewSharedEngine::SizeRootObjectToView); loadWallpaperPlugin(view); // overwrite the factory set by kdeclarative auto oldFactory = view->engine()->networkAccessManagerFactory(); view->engine()->setNetworkAccessManagerFactory(nullptr); delete oldFactory; view->engine()->setNetworkAccessManagerFactory(new NoAccessNetworkAccessManagerFactory); QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); lockProperty.write(m_immediateLock || (!m_noLock && !m_delayedLockTimer)); QQmlProperty sleepProperty(view->rootObject(), QStringLiteral("suspendToRamSupported")); sleepProperty.write(m_canSuspend); if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToRam()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToRam()), SLOT(suspendToRam())); } QQmlProperty hibernateProperty(view->rootObject(), QStringLiteral("suspendToDiskSupported")); hibernateProperty.write(m_canHibernate); if (view->rootObject() && view->rootObject()->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("suspendToDisk()").constData()) != -1) { connect(view->rootObject(), SIGNAL(suspendToDisk()), SLOT(suspendToDisk())); } // verify that the engine's controller didn't change Q_ASSERT(dynamic_cast(view->engine()->networkAccessManagerFactory())); m_views << view; } // update geometry of all views and savers for (int i = 0; i < nScreens; ++i) { auto *view = m_views.at(i); auto screen = QGuiApplication::screens()[i]; view->setGeometry(screen->geometry()); KWayland::Client::PlasmaShellSurface *plasmaSurface = view->property("plasmaShellSurface").value(); if (plasmaSurface) { plasmaSurface->setPosition(view->geometry().topLeft()); } if (auto object = view->property("wallpaperGraphicsObject").value()) { //initialize with our size to avoid as much resize events as possible object->completeInitialization({ {QStringLiteral("width"), view->width()}, {QStringLiteral("height"), view->height()} }); } connect(screen, &QScreen::geometryChanged, view, [view, plasmaSurface](const QRect &geo) { view->setGeometry(geo); if (plasmaSurface) { plasmaSurface->setPosition(view->geometry().topLeft()); } } ); if (m_testing) { view->show(); } else { // on Wayland we may not use fullscreen as that puts all windows on one screen if (plasmaSurface) { view->show(); } else { view->showFullScreen(); } } view->raise(); connect(view, &QQuickWindow::frameSwapped, this, [this, view] { markViewsAsVisible(view); }, Qt::QueuedConnection); } } void UnlockApp::markViewsAsVisible(KQuickAddons::QuickViewSharedEngine *view) { disconnect(view, &QQuickWindow::frameSwapped, this, 0); QQmlProperty showProperty(view->rootObject(), QStringLiteral("viewVisible")); showProperty.write(true); // random state update, actually rather required on init only QMetaObject::invokeMethod(this, "getFocus", Qt::QueuedConnection); QGuiApplication::clipboard()->clear(); QGuiApplication::clipboard()->clear(QClipboard::Selection); } void UnlockApp::getFocus() { if (m_views.isEmpty()) { return; } QWindow *w = 0; // this loop is required to make the qml/graphicsscene properly handle the shared keyboard input // ie. "type something into the box of every greeter" foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { if (!m_testing) { view->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! } // w->setFocus(Qt::OtherFocusReason); // FIXME } // determine which window should actually be active and have the real input focus/grab // FIXME - QWidget::underMouse() // foreach (QQuickView *view, m_views) { // if (view->underMouse()) { // w = view; // break; // } // } if (!w) { // try harder foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { if (view->geometry().contains(QCursor::pos())) { w = view; break; } } } if (!w) { // fallback solution w = m_views.first(); } // activate window and grab input to be sure it really ends up there. // focus setting is still required for proper internal QWidget state (and eg. visual reflection) if (!m_testing) { w->setKeyboardGrabEnabled(true); // TODO - check whether this still works in master! } w->requestActivate(); // w->setFocus(Qt::OtherFocusReason); // FIXME } void UnlockApp::setLockedPropertyOnViews() { delete m_delayedLockTimer; m_delayedLockTimer = 0; foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { QQmlProperty lockProperty(view->rootObject(), QStringLiteral("locked")); lockProperty.write(true); } } void UnlockApp::resetRequestIgnore() { m_ignoreRequests = false; } void UnlockApp::suspendToRam() { if (m_ignoreRequests) { return; } m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); org_kde_ksld_suspendSystem(m_ksldInterface); } void UnlockApp::suspendToDisk() { if (m_ignoreRequests) { return; } m_ignoreRequests = true; m_resetRequestIgnoreTimer->start(); org_kde_ksld_hibernateSystem(m_ksldInterface); } void UnlockApp::setTesting(bool enable) { m_testing = enable; if (m_views.isEmpty()) { return; } if (enable) { // remove bypass window manager hint foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { view->setFlags(view->flags() & ~Qt::X11BypassWindowManagerHint); } } else { foreach (KQuickAddons::QuickViewSharedEngine * view, m_views) { view->setFlags(view->flags() | Qt::X11BypassWindowManagerHint); } } } void UnlockApp::setTheme(const QString &theme) { if (theme.isEmpty() || !m_testing) { return; } m_packageName = theme; KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel")); package.setPath(m_packageName); m_mainQmlPath = QUrl::fromLocalFile(package.filePath("lockscreenmainscript")); } void UnlockApp::setImmediateLock(bool immediate) { m_immediateLock = immediate; } void UnlockApp::lockImmediately() { setImmediateLock(true); setLockedPropertyOnViews(); } bool UnlockApp::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Show) { KQuickAddons::QuickViewSharedEngine *view(0); foreach (KQuickAddons::QuickViewSharedEngine *v, m_views) { if (v == obj) { view = v; break; } } if (view && view->winId() && QX11Info::isPlatformX11()) { // showing greeter view window, set property static Atom tag = XInternAtom(QX11Info::display(), "_KDE_SCREEN_LOCKER", False); XChangeProperty(QX11Info::display(), view->winId(), tag, tag, 32, PropModeReplace, 0, 0); } // no further processing return false; } if (event->type() == QEvent::KeyPress) { // react if saver is visible shareEvent(event, qobject_cast(obj)); return false; // we don't care } else if (event->type() == QEvent::KeyRelease) { // conditionally reshow the saver QKeyEvent *ke = static_cast(event); if (ke->key() != Qt::Key_Escape) { shareEvent(event, qobject_cast(obj)); return false; // irrelevant } return true; // don't pass } return false; } /* * This function forwards an event from one greeter window to all others * It's used to have the keyboard operate on all greeter windows (on every screen) * at once so that the user gets visual feedback on the screen he's looking at - * even if the focus is actually on a powered off screen. */ void UnlockApp::shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from) { // from can be NULL any time (because the parameter is passed as qobject_cast) // m_views.contains(from) is atm. supposed to be true but required if any further // QQuickView are added (which are not part of m_views) // this makes "from" an optimization (nullptr check aversion) if (from && m_views.contains(from)) { // NOTICE any recursion in the event sharing will prevent authentication on multiscreen setups! // Any change in regarded event processing shall be tested thoroughly! removeEventFilter(this); // prevent recursion! const bool accepted = e->isAccepted(); // store state foreach (KQuickAddons::QuickViewSharedEngine *view, m_views) { if (view != from) { QCoreApplication::sendEvent(view, e); e->setAccepted(accepted); } } installEventFilter(this); } } void UnlockApp::setGraceTime(int milliseconds) { m_graceTime = milliseconds; if (milliseconds < 0 || m_delayedLockTimer || m_noLock || m_immediateLock) { return; } m_delayedLockTimer = new QTimer(this); m_delayedLockTimer->setSingleShot(true); connect(m_delayedLockTimer, &QTimer::timeout, this, &UnlockApp::setLockedPropertyOnViews); m_delayedLockTimer->start(m_graceTime); } void UnlockApp::setNoLock(bool noLock) { m_noLock = noLock; } void UnlockApp::setDefaultToSwitchUser(bool defaultToSwitchUser) { m_defaultToSwitchUser = defaultToSwitchUser; } static void osdProgress(void *data, org_kde_ksld *org_kde_ksld, const char *icon, int32_t percent, const char *text) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->osdProgress(QString::fromUtf8(icon), percent, QString::fromUtf8(text)); } static void osdText(void *data, org_kde_ksld *org_kde_ksld, const char *icon, const char *text) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->osdText(QString::fromUtf8(icon), QString::fromUtf8(text)); } static void canSuspend(void *data, org_kde_ksld *org_kde_ksld, uint suspend) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->updateCanSuspend(suspend); } static void canHibernate(void *data, org_kde_ksld *org_kde_ksld, uint hibernate) { Q_UNUSED(org_kde_ksld) reinterpret_cast(data)->updateCanHibernate(hibernate); } static const struct org_kde_ksld_listener s_listener { osdProgress, osdText, canSuspend, canHibernate }; void UnlockApp::setKsldSocket(int socket) { using namespace KWayland::Client; m_ksldConnection = new ConnectionThread; m_ksldConnection->setSocketFd(socket); m_ksldRegistry = new Registry(); EventQueue *queue = new EventQueue(m_ksldRegistry); connect(m_ksldRegistry, &Registry::interfaceAnnounced, this, [this, queue] (QByteArray interface, quint32 name, quint32 version) { if (interface != QByteArrayLiteral("org_kde_ksld")) { return; } m_ksldInterface = reinterpret_cast(wl_registry_bind(*m_ksldRegistry, name, &org_kde_ksld_interface, version)); queue->addProxy(m_ksldInterface); if (version >= 2) { org_kde_ksld_add_listener(m_ksldInterface, &s_listener, this); } for (auto v : m_views) { org_kde_ksld_x11window(m_ksldInterface, v->winId()); wl_display_flush(m_ksldConnection->display()); } } ); connect(m_ksldConnection, &ConnectionThread::connected, this, [this, queue] { m_ksldRegistry->create(m_ksldConnection); queue->setup(m_ksldConnection); m_ksldRegistry->setEventQueue(queue); m_ksldRegistry->setup(); wl_display_flush(m_ksldConnection->display()); }, Qt::QueuedConnection); m_ksldConnectionThread = new QThread(this); m_ksldConnection->moveToThread(m_ksldConnectionThread); m_ksldConnectionThread->start(); m_ksldConnection->initConnection(); } void UnlockApp::osdProgress(const QString &icon, int percent, const QString &additionalText) { for (auto v : m_views) { auto osd = v->rootObject()->findChild(QStringLiteral("onScreenDisplay")); if (!osd) { continue; } osd->setProperty("osdValue", percent); osd->setProperty("osdAdditionalText", additionalText); osd->setProperty("showingProgress", true); osd->setProperty("icon", icon); QMetaObject::invokeMethod(osd, "show"); } } void UnlockApp::osdText(const QString &icon, const QString &additionalText) { for (auto v : m_views) { auto osd = v->rootObject()->findChild(QStringLiteral("onScreenDisplay")); if (!osd) { continue; } osd->setProperty("showingProgress", false); osd->setProperty("osdValue", additionalText); osd->setProperty("icon", icon); QMetaObject::invokeMethod(osd, "show"); } } void UnlockApp::updateCanSuspend(bool set) { if (m_canSuspend == set) { return; } m_canSuspend = set; for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { QQmlProperty sleepProperty((*it)->rootObject(), QStringLiteral("suspendToRamSupported")); sleepProperty.write(m_canSuspend); } } void UnlockApp::updateCanHibernate(bool set) { if (m_canHibernate == set) { return; } m_canHibernate = set; for (auto it = m_views.constBegin(), end = m_views.constEnd(); it != end; ++it) { QQmlProperty hibernateProperty((*it)->rootObject(), QStringLiteral("suspendToDiskSupported")); hibernateProperty.write(m_canHibernate); } } } // namespace diff --git a/greeter/greeterapp.h b/greeter/greeterapp.h index 988438c..4e3ded3 100644 --- a/greeter/greeterapp.h +++ b/greeter/greeterapp.h @@ -1,118 +1,124 @@ /******************************************************************** KSld - the KDE Screenlocker Daemon This file is part of the KDE project. Copyright (C) 2011 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 . *********************************************************************/ #ifndef SCREENLOCKER_GREETERAPP_H #define SCREENLOCKER_GREETERAPP_H #include #include #include namespace KWayland { namespace Client { class ConnectionThread; class Registry; class PlasmaShell; } } namespace KQuickAddons { class QuickViewSharedEngine; } class Authenticator; struct org_kde_ksld; namespace ScreenLocker { class Unlocker; class WallpaperIntegration; class UnlockApp : public QGuiApplication { Q_OBJECT public: explicit UnlockApp(int &argc, char **argv); virtual ~UnlockApp(); void setTesting(bool enable); void setTheme(const QString &theme); void setImmediateLock(bool immediateLock); void lockImmediately(); void setGraceTime(int milliseconds); void setNoLock(bool noLock); void setKsldSocket(int socket); void setDefaultToSwitchUser(bool defaultToSwitchUser); void osdProgress(const QString &icon, int percent, const QString &additionalText); void osdText(const QString &icon, const QString &additionalText); void updateCanSuspend(bool set); void updateCanHibernate(bool set); + bool supportsSeccomp() const { + return m_supportsSeccomp; + } + public Q_SLOTS: void desktopResized(); protected: virtual bool eventFilter(QObject *obj, QEvent *event); private Q_SLOTS: void resetRequestIgnore(); void suspendToRam(); void suspendToDisk(); void getFocus(); void markViewsAsVisible(KQuickAddons::QuickViewSharedEngine *view); void setLockedPropertyOnViews(); private: void initialize(); void initializeWayland(); void shareEvent(QEvent *e, KQuickAddons::QuickViewSharedEngine *from); void loadWallpaperPlugin(KQuickAddons::QuickViewSharedEngine *view); + Authenticator *createAuthenticator(); QString m_packageName; QUrl m_mainQmlPath; QList m_views; QTimer *m_resetRequestIgnoreTimer; QTimer *m_delayedLockTimer; KPackage::Package m_package; bool m_testing; bool m_ignoreRequests; bool m_immediateLock; bool m_runtimeInitialized; Authenticator *m_authenticator; int m_graceTime; bool m_noLock; bool m_defaultToSwitchUser; bool m_canSuspend = false; bool m_canHibernate = false; KWayland::Client::ConnectionThread *m_ksldConnection = nullptr; KWayland::Client::Registry *m_ksldRegistry = nullptr; QThread *m_ksldConnectionThread = nullptr; org_kde_ksld *m_ksldInterface = nullptr; KWayland::Client::PlasmaShell *m_plasmaShell = nullptr; WallpaperIntegration *m_wallpaperIntegration; + bool m_supportsSeccomp = false; }; } // namespace #endif // SCREENLOCKER_GREETERAPP_H diff --git a/greeter/main.cpp b/greeter/main.cpp index 6ca8943..ff64a55 100644 --- a/greeter/main.cpp +++ b/greeter/main.cpp @@ -1,182 +1,193 @@ /******************************************************************** This file is part of the KDE project. Copyright (C) 2011 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 #include #include #include #include #include "greeterapp.h" #include #if HAVE_SYS_PRCTL_H #include #endif #if HAVE_SYS_PROCCTL_H #include #include #endif +#if HAVE_SECCOMP +#include "seccomp_filter.h" +#endif static void signalHandler(int signum) { ScreenLocker::UnlockApp *instance = qobject_cast(QCoreApplication::instance()); if (!instance) return; switch(signum) { case SIGTERM: // exit gracefully to not leave behind screensaver processes (bug#224200) // return exit code 1 to indicate that a valid password was not entered, // to prevent circumventing the password input by sending a SIGTERM instance->exit(1); break; case SIGUSR1: instance->lockImmediately(); break; } } int main(int argc, char* argv[]) { // disable ptrace on the greeter #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 0); #endif #if HAVE_PROC_TRACE_CTL int mode = PROC_TRACE_CTL_DISABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif KLocalizedString::setApplicationDomain("kscreenlocker_greet"); // explicitly disable input methods as it makes it impossible to unlock, see BUG 306932 // but explicitly set on screen keyboard such as maliit is allowed if (!qEnvironmentVariableIsSet("QT_IM_MODULE") || (qEnvironmentVariableIsSet("QT_IM_MODULE") && qgetenv("QT_IM_MODULE") != QByteArrayLiteral("maliit"))) { qputenv("QT_IM_MODULE", QByteArrayLiteral("qtvirtualkeyboard")); } ScreenLocker::UnlockApp app(argc, argv); app.setQuitOnLastWindowClosed(false); QCoreApplication::setApplicationName(QStringLiteral("kscreenlocker_greet")); QCoreApplication::setApplicationVersion(QStringLiteral("0.1")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); // disable session management for the greeter auto disableSessionManagement = [](QSessionManager &sm) { sm.setRestartHint(QSessionManager::RestartNever); }; QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement); QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement); QCommandLineParser parser; parser.setApplicationDescription(i18n("Greeter for the KDE Plasma Workspaces Screen locker")); parser.addHelpOption(); parser.addVersionOption(); QCommandLineOption testingOption(QStringLiteral("testing"), i18n("Starts the greeter in testing mode")); QCommandLineOption themeOption(QStringLiteral("theme"), i18n("Starts the greeter with the selected theme (only in Testing mode)"), QStringLiteral("theme"), QStringLiteral("")); QCommandLineOption immediateLockOption(QStringLiteral("immediateLock"), i18n("Lock immediately, ignoring any grace time etc.")); QCommandLineOption graceTimeOption(QStringLiteral("graceTime"), i18n("Delay till the lock user interface gets shown in milliseconds."), QStringLiteral("milliseconds"), QStringLiteral("0")); QCommandLineOption nolockOption(QStringLiteral("nolock"), i18n("Don't show any lock user interface.")); QCommandLineOption switchUserOption(QStringLiteral("switchuser"), i18n("Default to the switch user UI.")); QCommandLineOption waylandFdOption(QStringLiteral("ksldfd"), i18n("File descriptor for connecting to ksld."), QStringLiteral("fd")); parser.addOption(testingOption); parser.addOption(themeOption); parser.addOption(immediateLockOption); parser.addOption(graceTimeOption); parser.addOption(nolockOption); parser.addOption(switchUserOption); parser.addOption(waylandFdOption); parser.process(app); if (parser.isSet(testingOption)) { app.setTesting(true); app.setImmediateLock(true); //parse theme option const QString theme = parser.value(themeOption); if (!theme.isEmpty()) { app.setTheme(theme); } // allow ptrace if testing is enabled #if HAVE_PR_SET_DUMPABLE prctl(PR_SET_DUMPABLE, 1); #endif #if HAVE_PROC_TRACE_CTL int mode = PROC_TRACE_CTL_ENABLE; procctl(P_PID, getpid(), PROC_TRACE_CTL, &mode); #endif } else { app.setImmediateLock(parser.isSet(immediateLockOption)); } app.setNoLock(parser.isSet(nolockOption)); bool ok = false; int graceTime = parser.value(graceTimeOption).toInt(&ok); if (ok) { app.setGraceTime(graceTime); } if (parser.isSet(switchUserOption)) { app.setDefaultToSwitchUser(true); } if (parser.isSet(waylandFdOption)) { ok = false; const int fd = parser.value(waylandFdOption).toInt(&ok); if (ok) { 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. // Crucial for blocking until it is ready, ensuring locking happens before sleep, e.g. std::cout << "Locked at " << QDateTime::currentDateTime().toTime_t() << std::endl; struct sigaction sa; sa.sa_handler = signalHandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGTERM, &sa, nullptr); sigaction(SIGUSR1, &sa, nullptr); return app.exec(); } diff --git a/greeter/seccomp_filter.cpp b/greeter/seccomp_filter.cpp new file mode 100644 index 0000000..79244f9 --- /dev/null +++ b/greeter/seccomp_filter.cpp @@ -0,0 +1,84 @@ +/******************************************************************** + 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 +#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 + 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); +} + +} +} diff --git a/greeter/seccomp_filter.h b/greeter/seccomp_filter.h new file mode 100644 index 0000000..9ec07e5 --- /dev/null +++ b/greeter/seccomp_filter.h @@ -0,0 +1,36 @@ +/******************************************************************** + 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 . +*********************************************************************/ +#ifndef SCREENLOCKER_SECCOMP_FILTER_H +#define SCREENLOCKER_SECCOMP_FILTER_H + +namespace ScreenLocker +{ +namespace SecComp +{ + +void init(); + +} +} + +#endif