Commit 2136a38d authored by Martin Flöser's avatar Martin Flöser

Don't dissallow open with write flag syscall on NVIDIA

Summary:
The latest NVIDIA driver crashes the greeter due to our seccomp enabled
sandbox being too restrictive. The driver is now opening files for
writing after our dummy context got created and this causes a crash. In
order to provide our users a working system again we better disable the
seccomp rule for NVIDIA users for the time being.

To detect whether an NVIDIA driver is used I copied the glplatform from
KWin which is known to work and more reliable than writing new custom
code even if it's a code copy. For master I'll look into splitting that
one out from KWin and putting it into a dedicated library so that we can
link it.

This of course means that the seccomp based sandbox is now incomplete
for NVIDIA users. An idea is to add an additional apparmor rule in
master to enforce the write restrictions in similar way without forcing
it for /dev.

BUG: 384005

Test Plan: I don't have an NVIDIA

Reviewers: #plasma

Subscribers: plasma-devel

Tags: #plasma

Differential Revision: https://phabricator.kde.org/D7616
parent 215d4600
......@@ -13,6 +13,7 @@ set(kscreenlocker_greet_SRCS
main.cpp
noaccessnetworkaccessmanagerfactory.cpp
wallpaper_integration.cpp
kwinglplatform.cpp
)
if(HAVE_SECCOMP)
......
......@@ -32,7 +32,7 @@ target_link_libraries(killTest Qt5::Test)
# Seccomp Test
#######################################
if(HAVE_SECCOMP)
add_executable(seccompTest seccomp_test.cpp ../seccomp_filter.cpp)
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
......
......@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
#include <config-kscreenlocker.h>
#include "../seccomp_filter.h"
#include "../kwinglplatform.h"
#include <QtTest/QtTest>
#include <QTemporaryFile>
......@@ -55,6 +56,9 @@ void SeccompTest::testCreateFile()
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));
......
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project.
Copyright (C) 2010 Fredrik Höglund <fredrik@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 "kwinglplatform.h"
#include <QRegExp>
#include <QStringList>
#include <QDebug>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <sys/utsname.h>
#include <iostream>
#include <iomanip>
#include <ios>
namespace KWin
{
GLPlatform *GLPlatform::s_platform = 0;
static qint64 parseVersionString(const QByteArray &version)
{
// Skip any leading non digit
int start = 0;
while (start < version.length() && !QChar::fromLatin1(version[start]).isDigit())
start++;
// Strip any non digit, non '.' characters from the end
int end = start;
while (end < version.length() && (version[end] == '.' || QChar::fromLatin1(version[end]).isDigit()))
end++;
const QByteArray result = version.mid(start, end-start);
const QList<QByteArray> tokens = result.split('.');
const qint64 major = tokens.at(0).toInt();
const qint64 minor = tokens.count() > 1 ? tokens.at(1).toInt() : 0;
const qint64 patch = tokens.count() > 2 ? tokens.at(2).toInt() : 0;
return kVersionNumber(major, minor, patch);
}
static qint64 getKernelVersion()
{
struct utsname name;
uname(&name);
if (qstrcmp(name.sysname, "Linux") == 0)
return parseVersionString(name.release);
return 0;
}
// Extracts the portion of a string that matches a regular expression
static QString extract(const QString &string, const QString &match, int offset = 0)
{
QString result;
QRegExp rx(match);
int pos = rx.indexIn(string, offset);
if (pos != -1)
result = string.mid(pos, rx.matchedLength());
return result;
}
static ChipClass detectRadeonClass(const QByteArray &chipset)
{
if (chipset.isEmpty())
return UnknownRadeon;
if (chipset.contains("R100") ||
chipset.contains("RV100") ||
chipset.contains("RS100"))
return R100;
if (chipset.contains("RV200") ||
chipset.contains("RS200") ||
chipset.contains("R200") ||
chipset.contains("RV250") ||
chipset.contains("RS300") ||
chipset.contains("RV280"))
return R200;
if (chipset.contains("R300") ||
chipset.contains("R350") ||
chipset.contains("R360") ||
chipset.contains("RV350") ||
chipset.contains("RV370") ||
chipset.contains("RV380"))
return R300;
if (chipset.contains("R420") ||
chipset.contains("R423") ||
chipset.contains("R430") ||
chipset.contains("R480") ||
chipset.contains("R481") ||
chipset.contains("RV410") ||
chipset.contains("RS400") ||
chipset.contains("RC410") ||
chipset.contains("RS480") ||
chipset.contains("RS482") ||
chipset.contains("RS600") ||
chipset.contains("RS690") ||
chipset.contains("RS740"))
return R400;
if (chipset.contains("RV515") ||
chipset.contains("R520") ||
chipset.contains("RV530") ||
chipset.contains("R580") ||
chipset.contains("RV560") ||
chipset.contains("RV570"))
return R500;
if (chipset.contains("R600") ||
chipset.contains("RV610") ||
chipset.contains("RV630") ||
chipset.contains("RV670") ||
chipset.contains("RV620") ||
chipset.contains("RV635") ||
chipset.contains("RS780") ||
chipset.contains("RS880"))
return R600;
if (chipset.contains("R700") ||
chipset.contains("RV770") ||
chipset.contains("RV730") ||
chipset.contains("RV710") ||
chipset.contains("RV740"))
return R700;
if (chipset.contains("EVERGREEN") || // Not an actual chipset, but returned by R600G in 7.9
chipset.contains("CEDAR") ||
chipset.contains("REDWOOD") ||
chipset.contains("JUNIPER") ||
chipset.contains("CYPRESS") ||
chipset.contains("HEMLOCK") ||
chipset.contains("PALM"))
return Evergreen;
if (chipset.contains("SUMO") ||
chipset.contains("SUMO2") ||
chipset.contains("BARTS") ||
chipset.contains("TURKS") ||
chipset.contains("CAICOS") ||
chipset.contains("CAYMAN"))
return NorthernIslands;
const QString chipset16 = QString::fromLatin1(chipset);
QString name = extract(chipset16, QStringLiteral("HD [0-9]{4}")); // HD followed by a space and 4 digits
if (!name.isEmpty()) {
const int id = name.rightRef(4).toInt();
if (id == 6250 || id == 6310) // Palm
return Evergreen;
if (id >= 6000 && id < 7000)
return NorthernIslands; // HD 6xxx
if (id >= 5000 && id < 6000)
return Evergreen; // HD 5xxx
if (id >= 4000 && id < 5000)
return R700; // HD 4xxx
if (id >= 2000 && id < 4000) // HD 2xxx/3xxx
return R600;
return UnknownRadeon;
}
name = extract(chipset16, QStringLiteral("X[0-9]{3,4}")); // X followed by 3-4 digits
if (!name.isEmpty()) {
const int id = name.midRef(1, -1).toInt();
// X1xxx
if (id >= 1300)
return R500;
// X7xx, X8xx, X12xx, 2100
if ((id >= 700 && id < 1000) || id >= 1200)
return R400;
// X200, X3xx, X5xx, X6xx, X10xx, X11xx
if ((id >= 300 && id < 700) || (id >= 1000 && id < 1200))
return R300;
return UnknownRadeon;
}
name = extract(chipset16, QStringLiteral("\\b[0-9]{4}\\b")); // A group of 4 digits
if (!name.isEmpty()) {
const int id = name.toInt();
// 7xxx
if (id >= 7000 && id < 8000)
return R100;
// 8xxx, 9xxx
if (id >= 8000 && id < 9500)
return R200;
// 9xxx
if (id >= 9500)
return R300;
if (id == 2100)
return R400;
}
return UnknownRadeon;
}
static ChipClass detectNVidiaClass(const QString &chipset)
{
QString name = extract(chipset, QStringLiteral("\\bNV[0-9,A-F]{2}\\b")); // NV followed by two hexadecimal digits
if (!name.isEmpty()) {
const int id = chipset.midRef(2, -1).toInt(0, 16); // Strip the 'NV' from the id
switch(id & 0xf0) {
case 0x00:
case 0x10:
return NV10;
case 0x20:
return NV20;
case 0x30:
return NV30;
case 0x40:
case 0x60:
return NV40;
case 0x50:
case 0x80:
case 0x90:
case 0xA0:
return G80;
default:
return UnknownNVidia;
}
}
if (chipset.contains(QLatin1String("GeForce2")) || chipset.contains(QLatin1String("GeForce 256")))
return NV10;
if (chipset.contains(QLatin1String("GeForce3")))
return NV20;
if (chipset.contains(QLatin1String("GeForce4"))) {
if (chipset.contains(QLatin1String("MX 420")) ||
chipset.contains(QLatin1String("MX 440")) || // including MX 440SE
chipset.contains(QLatin1String("MX 460")) ||
chipset.contains(QLatin1String("MX 4000")) ||
chipset.contains(QLatin1String("PCX 4300")))
return NV10;
return NV20;
}
// GeForce 5,6,7,8,9
name = extract(chipset, QStringLiteral("GeForce (FX |PCX |Go )?\\d{4}(M|\\b)")).trimmed();
if (!name.isEmpty()) {
if (!name[name.length() - 1].isDigit())
name.chop(1);
const int id = name.rightRef(4).toInt();
if (id < 6000)
return NV30;
if (id >= 6000 && id < 8000)
return NV40;
if (id >= 8000)
return G80;
return UnknownNVidia;
}
// GeForce 100/200/300/400/500
name = extract(chipset, QStringLiteral("GeForce (G |GT |GTX |GTS )?\\d{3}(M|\\b)")).trimmed();
if (!name.isEmpty()) {
if (!name[name.length() - 1].isDigit())
name.chop(1);
const int id = name.rightRef(3).toInt();
if (id >= 100 && id < 600) {
if (id >= 400)
return GF100;
return G80;
}
return UnknownNVidia;
}
return UnknownNVidia;
}
static inline ChipClass detectNVidiaClass(const QByteArray &chipset)
{
return detectNVidiaClass(QString::fromLatin1(chipset));
}
static ChipClass detectIntelClass(const QByteArray &chipset)
{
// see mesa repository: src/mesa/drivers/dri/intel/intel_context.c
// GL 1.3, DX8? SM ?
if (chipset.contains("845G") ||
chipset.contains("830M") ||
chipset.contains("852GM/855GM") ||
chipset.contains("865G"))
return I8XX;
// GL 1.4, DX 9.0, SM 2.0
if (chipset.contains("915G") ||
chipset.contains("E7221G") ||
chipset.contains("915GM") ||
chipset.contains("945G") || // DX 9.0c
chipset.contains("945GM") ||
chipset.contains("945GME") ||
chipset.contains("Q33") || // GL1.5
chipset.contains("Q35") ||
chipset.contains("G33") ||
chipset.contains("965Q") || // GMA 3000, but apparently considered gen 4 by the driver
chipset.contains("946GZ") || // GMA 3000, but apparently considered gen 4 by the driver
chipset.contains("IGD"))
return I915;
// GL 2.0, DX 9.0c, SM 3.0
if (chipset.contains("965G") ||
chipset.contains("G45/G43") || // SM 4.0
chipset.contains("965GM") || // GL 2.1
chipset.contains("965GME/GLE") ||
chipset.contains("GM45") ||
chipset.contains("Q45/Q43") ||
chipset.contains("G41") ||
chipset.contains("B43") ||
chipset.contains("Ironlake"))
return I965;
// GL 3.1, CL 1.1, DX 10.1
if (chipset.contains("Sandybridge")) {
return SandyBridge;
}
// GL4.0, CL1.1, DX11, SM 5.0
if (chipset.contains("Ivybridge")) {
return IvyBridge;
}
// GL4.0, CL1.2, DX11.1, SM 5.0
if (chipset.contains("Haswell")) {
return Haswell;
}
return UnknownIntel;
}
static ChipClass detectQualcommClass(const QByteArray &chipClass)
{
if (!chipClass.contains("Adreno")) {
return UnknownChipClass;
}
const auto parts = chipClass.split(' ');
if (parts.count() < 3) {
return UnknownAdreno;
}
bool ok = false;
const int value = parts.at(2).toInt(&ok);
if (ok) {
if (value >= 100 && value < 200) {
return Adreno1XX;
}
if (value >= 200 && value < 300) {
return Adreno2XX;
}
if (value >= 300 && value < 400) {
return Adreno3XX;
}
if (value >= 400 && value < 500) {
return Adreno4XX;
}
if (value >= 500 && value < 600) {
return Adreno5XX;
}
}
return UnknownAdreno;
}
QString GLPlatform::versionToString(qint64 version)
{
return QString::fromLatin1(versionToString8(version));
}
QByteArray GLPlatform::versionToString8(qint64 version)
{
int major = (version >> 32);
int minor = (version >> 16) & 0xffff;
int patch = version & 0xffff;
QByteArray string = QByteArray::number(major) + '.' + QByteArray::number(minor);
if (patch != 0)
string += '.' + QByteArray::number(patch);
return string;
}
QString GLPlatform::driverToString(Driver driver)
{
return QString::fromLatin1(driverToString8(driver));
}
QByteArray GLPlatform::driverToString8(Driver driver)
{
switch(driver) {
case Driver_R100:
return QByteArrayLiteral("Radeon");
case Driver_R200:
return QByteArrayLiteral("R200");
case Driver_R300C:
return QByteArrayLiteral("R300C");
case Driver_R300G:
return QByteArrayLiteral("R300G");
case Driver_R600C:
return QByteArrayLiteral("R600C");
case Driver_R600G:
return QByteArrayLiteral("R600G");
case Driver_Nouveau:
return QByteArrayLiteral("Nouveau");
case Driver_Intel:
return QByteArrayLiteral("Intel");
case Driver_NVidia:
return QByteArrayLiteral("NVIDIA");
case Driver_Catalyst:
return QByteArrayLiteral("Catalyst");
case Driver_Swrast:
return QByteArrayLiteral("Software rasterizer");
case Driver_Softpipe:
return QByteArrayLiteral("softpipe");
case Driver_Llvmpipe:
return QByteArrayLiteral("LLVMpipe");
case Driver_VirtualBox:
return QByteArrayLiteral("VirtualBox (Chromium)");
case Driver_VMware:
return QByteArrayLiteral("VMware (SVGA3D)");
case Driver_Qualcomm:
return QByteArrayLiteral("Qualcomm");
default:
return QByteArrayLiteral("Unknown");
}
}
QString GLPlatform::chipClassToString(ChipClass chipClass)
{
return QString::fromLatin1(chipClassToString8(chipClass));
}
QByteArray GLPlatform::chipClassToString8(ChipClass chipClass)
{
switch(chipClass) {
case R100:
return QByteArrayLiteral("R100");
case R200:
return QByteArrayLiteral("R200");
case R300:
return QByteArrayLiteral("R300");
case R400:
return QByteArrayLiteral("R400");
case R500:
return QByteArrayLiteral("R500");
case R600:
return QByteArrayLiteral("R600");
case R700:
return QByteArrayLiteral("R700");
case Evergreen:
return QByteArrayLiteral("EVERGREEN");
case NorthernIslands:
return QByteArrayLiteral("NI");
case NV10:
return QByteArrayLiteral("NV10");
case NV20:
return QByteArrayLiteral("NV20");
case NV30:
return QByteArrayLiteral("NV30");
case NV40:
return QByteArrayLiteral("NV40/G70");
case G80:
return QByteArrayLiteral("G80/G90");
case GF100:
return QByteArrayLiteral("GF100");
case I8XX:
return QByteArrayLiteral("i830/i835");
case I915:
return QByteArrayLiteral("i915/i945");
case I965:
return QByteArrayLiteral("i965");
case SandyBridge:
return QByteArrayLiteral("SandyBridge");
case IvyBridge:
return QByteArrayLiteral("IvyBridge");
case Haswell:
return QByteArrayLiteral("Haswell");
case Adreno1XX:
return QByteArrayLiteral("Adreno 1xx series");
case Adreno2XX:
return QByteArrayLiteral("Adreno 2xx series");
case Adreno3XX:
return QByteArrayLiteral("Adreno 3xx series");
case Adreno4XX:
return QByteArrayLiteral("Adreno 4xx series");
case Adreno5XX:
return QByteArrayLiteral("Adreno 5xx series");
default:
return QByteArrayLiteral("Unknown");
}
}
// -------
GLPlatform::GLPlatform()
: m_driver(Driver_Unknown),
m_chipClass(UnknownChipClass),
m_glVersion(0),
m_glslVersion(0),
m_mesaVersion(0),
m_driverVersion(0),
m_galliumVersion(0),
m_serverVersion(0),
m_kernelVersion(0),
m_looseBinding(false),
m_supportsGLSL(false),
m_limitedGLSL(false),
m_textureNPOT(false),
m_limitedNPOT(false),
m_virtualMachine(false),
m_preferBufferSubData(false),
m_gles(false)
{
}
GLPlatform::~GLPlatform()
{
}
void GLPlatform::detect()
{
QOpenGLFunctions gl;
gl.initializeOpenGLFunctions();
m_vendor = (const char*)gl.glGetString(GL_VENDOR);
m_renderer = (const char*)gl.glGetString(GL_RENDERER);
m_version = (const char*)gl.glGetString(GL_VERSION);
// Parse the OpenGL version
const QList<QByteArray> versionTokens = m_version.split(' ');
if (versionTokens.count() > 0) {
const QByteArray version = QByteArray(m_version);
m_glVersion = parseVersionString(version);
if (version.startsWith("OpenGL ES")) {
// from GLES 2: "Returns a version or release number of the form OpenGL<space>ES<space><version number><space><vendor-specific information>."
// from GLES 3: "Returns a version or release number." and "The version number uses one of these forms: major_number.minor_number major_number.minor_number.release_number"
m_gles = true;
}
}
const QByteArray extensions = (const char *) gl.glGetString(GL_EXTENSIONS);
m_extensions = QSet<QByteArray>::fromList(extensions.split(' '));
// Parse the Mesa version
const int mesaIndex = versionTokens.indexOf("Mesa");
if (mesaIndex != -1) {
const QByteArray version = versionTokens.at(mesaIndex + 1);
m_mesaVersion = parseVersionString(version);
}
if (isGLES()) {
m_supportsGLSL = true;
m_textureNPOT = true;
} else {
m_supportsGLSL = m_extensions.contains("GL_ARB_shader_objects") &&