Merge remote-tracking branch 'origin/more-permissions'

FIXES #1190
FIXES #1191
main
Adriaan de Groot 6 years ago
commit c9b675cbc6

@ -16,7 +16,7 @@ understanding the issues (alphabetically by first name):
- Seth Arnold
- Simon Quigley
- Thomas Ward
Both CVE's have been resolved.
## Core ##
@ -26,7 +26,7 @@ No core changes.
- *initramfs* could create an initramfs with insecure permissions.
Since the keyfile is included in the initramfs, an attacker could
read the file from the initramfs. #1190
read the file from the initramfs. #1190 CVE-2019-13178
- *luksbootkeyfile* created a key file where a window of opportunity
existed where the key file could have too-lax file permissions.
#1191 CVE-2019-13179

@ -85,8 +85,10 @@ System::System( bool doChroot, QObject* parent )
{
Q_ASSERT( !s_instance );
s_instance = this;
if ( !doChroot )
if ( !doChroot && Calamares::JobQueue::instance() && Calamares::JobQueue::instance()->globalStorage() )
{
Calamares::JobQueue::instance()->globalStorage()->insert( "rootMountPoint", "/" );
}
}
@ -243,6 +245,69 @@ System::runCommand(
return ProcessResult(r, output);
}
QString
System::targetPath( const QString& path ) const
{
QString completePath;
if ( doChroot() )
{
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance() ? Calamares::JobQueue::instance()->globalStorage() : nullptr;
if ( !gs || !gs->contains( "rootMountPoint" ) )
{
cWarning() << "No rootMountPoint in global storage, cannot create target file" << path;
return QString();
}
completePath = gs->value( "rootMountPoint" ).toString() + '/' + path;
}
else
{
completePath = QStringLiteral( "/" ) + path;
}
return completePath;
}
QString
System::createTargetFile( const QString& path, const QByteArray& contents ) const
{
QString completePath = targetPath( path );
if ( completePath.isEmpty() )
{
return QString();
}
QFile f( completePath );
if ( f.exists() )
{
return QString();
}
QIODevice::OpenMode m =
#if QT_VERSION >= QT_VERSION_CHECK( 5, 11, 0 )
// New flag from Qt 5.11, implies WriteOnly
QIODevice::NewOnly |
#endif
QIODevice::WriteOnly | QIODevice::Truncate;
if ( !f.open( m ) )
{
return QString();
}
if ( f.write( contents ) != contents.size() )
{
f.close();
f.remove();
return QString();
}
f.close();
return QFileInfo( f ).canonicalFilePath();
}
QPair<quint64, float>
System::getTotalMemoryB() const

@ -205,6 +205,40 @@ public:
return targetEnvOutput( QStringList{ command }, output, workingPath, stdInput, timeoutSec );
}
/** @brief Gets a path to a file in the target system, from the host.
*
* @param path Path to the file; this is interpreted
* from the root of the target system (whatever that may be,
* but / in the chroot, or / in OEM modes).
*
* @return The complete path to the target file, from
* the root of the host system, or empty on failure.
*
* For instance, during installation where the target root is
* mounted on /tmp/calamares-something, asking for targetPath("/etc/passwd")
* will give you /tmp/calamares-something/etc/passwd.
*
* No attempt is made to canonicalize anything, since paths might not exist.
*/
DLLEXPORT QString targetPath( const QString& path ) const;
/** @brief Create a (small-ish) file in the target system.
*
* @param path Path to the file; this is interpreted
* from the root of the target system (whatever that may be,
* but / in the chroot, or / in OEM modes).
* @param contents Actual content of the file.
*
* Will not overwrite files. Returns an empty string if the
* target file already exists.
*
* @return The complete canonical path to the target file from the
* root of the host system, or empty on failure. (Here, it is
* possible to be canonical because the file exists).
*/
DLLEXPORT QString createTargetFile( const QString& path, const QByteArray& contents ) const;
/**
* @brief getTotalMemoryB returns the total main memory, in bytes.
*

@ -0,0 +1,26 @@
calamares_add_plugin( initcpio
TYPE job
EXPORT_MACRO PLUGINDLLEXPORT_PRO
SOURCES
InitcpioJob.cpp
LINK_PRIVATE_LIBRARIES
calamares
SHARED_LIB
)
if( ECM_FOUND AND BUILD_TESTING )
ecm_add_test(
Tests.cpp
TEST_NAME
initcpiotest
LINK_LIBRARIES
${CALAMARES_LIBRARIES}
calamares
calamares_job_initcpio # From above
${YAMLCPP_LIBRARY}
Qt5::Core
Qt5::Test
)
set_target_properties( initcpiotest PROPERTIES AUTOMOC TRUE )
target_include_directories( initcpiotest PRIVATE /usr/local/include )
endif()

@ -0,0 +1,108 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "InitcpioJob.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include "utils/UMask.h"
#include "utils/Variant.h"
#include <QDir>
#include <QFile>
InitcpioJob::InitcpioJob( QObject* parent )
: Calamares::CppJob( parent )
{
}
InitcpioJob::~InitcpioJob() {}
QString
InitcpioJob::prettyName() const
{
return tr( "Creating initramfs with mkinitcpio." );
}
void
fixPermissions( const QDir& d )
{
for ( const auto& fi : d.entryInfoList( { "initramfs*" }, QDir::Files ) )
{
QFile f( fi.absoluteFilePath() );
if ( f.exists() )
{
cDebug() << "initcpio fixing permissions for" << f.fileName();
f.setPermissions( QFileDevice::ReadOwner | QFileDevice::WriteOwner );
}
}
}
Calamares::JobResult
InitcpioJob::exec()
{
CalamaresUtils::UMask m( CalamaresUtils::UMask::Safe );
if ( m_unsafe )
{
cDebug() << "Skipping mitigations for unsafe initramfs permissions.";
}
else
{
QDir d( CalamaresUtils::System::instance()->targetPath( "/boot" ) );
if ( d.exists() )
{
fixPermissions( d );
}
}
cDebug() << "Updating initramfs with kernel" << m_kernel;
auto r = CalamaresUtils::System::instance()->targetEnvCommand(
{ "mkinitcpio", "-p", m_kernel }, QString(), QString(), 0 );
return r.explainProcess( "mkinitcpio", 10 );
}
void
InitcpioJob::setConfigurationMap( const QVariantMap& configurationMap )
{
m_kernel = CalamaresUtils::getString( configurationMap, "kernel" );
if ( m_kernel.isEmpty() )
{
m_kernel = QStringLiteral( "all" );
}
else if ( m_kernel == "$uname" )
{
auto r = CalamaresUtils::System::runCommand(
CalamaresUtils::System::RunLocation::RunInHost, { "/bin/uname", "-r" }, QString(), QString(), 3 );
if ( r.getExitCode() == 0 )
{
m_kernel = r.getOutput();
cDebug() << "*initcpio* using running kernel" << m_kernel;
}
else
{
cWarning() << "*initcpio* could not determine running kernel, using 'all'." << Logger::Continuation
<< r.getExitCode() << r.getOutput();
}
}
m_unsafe = CalamaresUtils::getBool( configurationMap, "be_unsafe", false );
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( InitcpioJobFactory, registerPlugin< InitcpioJob >(); )

@ -0,0 +1,50 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef INITCPIOJOB_H
#define INITCPIOJOB_H
#include "CppJob.h"
#include "PluginDllMacro.h"
#include "utils/PluginFactory.h"
#include <QObject>
#include <QVariantMap>
class PLUGINDLLEXPORT InitcpioJob : public Calamares::CppJob
{
Q_OBJECT
public:
explicit InitcpioJob( QObject* parent = nullptr );
virtual ~InitcpioJob() override;
QString prettyName() const override;
Calamares::JobResult exec() override;
void setConfigurationMap( const QVariantMap& configurationMap ) override;
private:
QString m_kernel;
bool m_unsafe = false;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( InitcpioJobFactory )
#endif // INITCPIOJOB_H

@ -0,0 +1,59 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Tests.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/Logger.h"
#include "utils/Yaml.h"
#include <QtTest/QtTest>
#include <QFileInfo>
#include <QStringList>
extern void fixPermissions( const QDir& d );
QTEST_GUILESS_MAIN( InitcpioTests )
InitcpioTests::InitcpioTests()
{
}
InitcpioTests::~InitcpioTests()
{
}
void
InitcpioTests::initTestCase()
{
}
void InitcpioTests::testFixPermissions()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
cDebug() << "Fixing up /boot";
fixPermissions( QDir( "/boot" ) );
cDebug() << "Fixing up /nonexistent";
fixPermissions( QDir( "/nonexistent/nonexistent" ) );
QVERIFY( true );
}

@ -0,0 +1,36 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TESTS_H
#define TESTS_H
#include <QObject>
class InitcpioTests : public QObject
{
Q_OBJECT
public:
InitcpioTests();
~InitcpioTests() override;
private Q_SLOTS:
void initTestCase();
void testFixPermissions();
};
#endif

@ -1,3 +1,23 @@
# Run mkinitcpio(8) with the given preset value
---
# There is only one configuration item for this module,
# the kernel to be loaded. This can have the following
# values:
# - empty or unset, interpreted as "all"
# - the literal string "$uname" (without quotes, with dollar),
# which will use the output of `uname -r` to determine the
# running kernel, and use that.
# - any other string.
#
# Whatever is set, that string is passed as *preset* argument to the
# `-p` option of *mkinitcpio*. Take care that both "$uname" operates
# in the host system, and might not be correct if the target system is
# updated (to a newer kernel) as part of the installation.
#
# Note that "all" is probably not a good preset to use either.
kernel: linux312
# Set this to true to turn off mitigations for lax file
# permissions on initramfs (which, in turn, can compromise
# your LUKS encryption keys, CVS-2019-13179).
be_unsafe: false

@ -1,50 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# === This file is part of Calamares - <https://github.com/calamares> ===
#
# Copyright 2014, Philip Müller <philm@manjaro.org>
# Copyright 2019, Adriaan de Groot <groot@kde.org>
#
# Calamares 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 3 of the License, or
# (at your option) any later version.
#
# Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
import libcalamares
from libcalamares.utils import check_target_env_call
import gettext
_ = gettext.translation("calamares-python",
localedir=libcalamares.utils.gettext_path(),
languages=libcalamares.utils.gettext_languages(),
fallback=True).gettext
def pretty_name():
return _("Creating initramfs with mkinitcpio.")
def run():
""" Calls routine to create kernel initramfs image.
:return:
"""
from subprocess import CalledProcessError
kernel = libcalamares.job.configuration['kernel']
try:
check_target_env_call(['mkinitcpio', '-p', kernel])
except CalledProcessError as e:
libcalamares.utils.warning(str(e))
return ( _( "Process Failed" ),
_( "Process <pre>mkinitcpio</pre> failed with error code {!s}. The command was <pre>{!s}</pre>." ).format( e.returncode, e.cmd ) )
return None

@ -1,5 +0,0 @@
---
type: "job"
name: "initcpio"
interface: "python"
script: "main.py"

@ -7,3 +7,20 @@ calamares_add_plugin( initramfs
calamares
SHARED_LIB
)
if( ECM_FOUND AND BUILD_TESTING )
ecm_add_test(
Tests.cpp
TEST_NAME
initramfstest
LINK_LIBRARIES
${CALAMARES_LIBRARIES}
calamares
calamares_job_initramfs # From above
${YAMLCPP_LIBRARY}
Qt5::Core
Qt5::Test
)
set_target_properties( initramfstest PROPERTIES AUTOMOC TRUE )
target_include_directories( initramfstest PRIVATE /usr/local/include )
endif()

@ -44,8 +44,26 @@ InitramfsJob::exec()
CalamaresUtils::UMask m( CalamaresUtils::UMask::Safe );
cDebug() << "Updating initramfs with kernel" << m_kernel;
if ( m_unsafe )
{
cDebug() << "Skipping mitigations for unsafe initramfs permissions.";
}
else
{
// First make sure we generate a safe initramfs with suitable permissions.
static const char confFile[] = "/etc/initramfs-tools/conf.d/calamares-safe-initramfs.conf";
static const char contents[] = "UMASK=0077\n";
if ( CalamaresUtils::System::instance()->createTargetFile( confFile, QByteArray( contents ) ).isEmpty() )
{
cWarning() << Logger::SubEntry << "Could not configure safe UMASK for initramfs.";
// But continue anyway.
}
}
// And then do the ACTUAL work.
auto r = CalamaresUtils::System::instance()->targetEnvCommand(
{ "update-initramfs", "-k", m_kernel, "-c", "-t" }, QString(), QString(), 120 );
{ "update-initramfs", "-k", m_kernel, "-c", "-t" }, QString(), QString(), 0 );
return r.explainProcess( "update-initramfs", 10 );
}
@ -73,6 +91,8 @@ InitramfsJob::setConfigurationMap( const QVariantMap& configurationMap )
<< r.getExitCode() << r.getOutput();
}
}
m_unsafe = CalamaresUtils::getBool( configurationMap, "be_unsafe", false );
}
CALAMARES_PLUGIN_FACTORY_DEFINITION( InitramfsJobFactory, registerPlugin< InitramfsJob >(); )

@ -42,6 +42,7 @@ public:
private:
QString m_kernel;
bool m_unsafe = false;
};
CALAMARES_PLUGIN_FACTORY_DECLARATION( InitramfsJobFactory )

@ -0,0 +1,96 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Tests.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "Settings.h"
#include "utils/CalamaresUtilsSystem.h"
#include "utils/Logger.h"
#include "utils/Yaml.h"
#include <QtTest/QtTest>
#include <QFileInfo>
#include <QStringList>
QTEST_GUILESS_MAIN( InitramfsTests )
InitramfsTests::InitramfsTests()
{
}
InitramfsTests::~InitramfsTests()
{
}
void
InitramfsTests::initTestCase()
{
Logger::setupLogLevel( Logger::LOGDEBUG );
}
static const char contents[] = "UMASK=0077\n";
static const char confFile[] = "/tmp/calamares-safe-umask";
void InitramfsTests::cleanup()
{
QFile::remove( confFile );
}
void InitramfsTests::testCreateHostFile()
{
CalamaresUtils::System s( false ); // don't chroot
QString path = s.createTargetFile( confFile, QByteArray( contents ) );
QVERIFY( !path.isEmpty() );
QCOMPARE( path, confFile ); // don't chroot, so path create relative to /
QVERIFY( QFile::exists( confFile ) );
QFileInfo fi( confFile );
QVERIFY( fi.exists() );
QCOMPARE( fi.size(), sizeof( contents )-1 ); // don't count trailing NUL
QFile::remove( confFile );
}
void InitramfsTests::testCreateTargetFile()
{
static const char short_confFile[] = "/calamares-safe-umask";
CalamaresUtils::System s( true );
QString path = s.createTargetFile( short_confFile, QByteArray( contents ) );
QVERIFY( path.isEmpty() ); // because no rootmountpoint is set
Calamares::JobQueue j;
j.globalStorage()->insert( "rootMountPoint", "/tmp" );
path = s.createTargetFile( short_confFile, QByteArray( contents ) );
QVERIFY( path.endsWith( short_confFile ) ); // chroot, so path create relative to
QVERIFY( path.startsWith( "/tmp/" ) );
QVERIFY( QFile::exists( path ) );
QFileInfo fi( path );
QVERIFY( fi.exists() );
QCOMPARE( fi.size(), sizeof( contents )-1 ); // don't count trailing NUL
QFile::remove( path );
}

@ -0,0 +1,39 @@
/* === This file is part of Calamares - <https://github.com/calamares> ===
*
* Copyright 2019, Adriaan de Groot <groot@kde.org>
*
* Calamares 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 3 of the License, or
* (at your option) any later version.
*
* Calamares 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 Calamares. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TESTS_H
#define TESTS_H
#include <QObject>
class InitramfsTests : public QObject
{
Q_OBJECT
public:
InitramfsTests();
~InitramfsTests() override;
private Q_SLOTS:
void initTestCase();
void cleanup();
void testCreateHostFile();
void testCreateTargetFile();
};
#endif

@ -29,3 +29,8 @@
# 3.2.9 and earlier which passed "all" as version.
kernel: "all"
# Set this to true to turn off mitigations for lax file
# permissions on initramfs (which, in turn, can compromise
# your LUKS encryption keys, CVS-2019-13179).
be_unsafe: false

@ -66,6 +66,31 @@ struct LuksDevice
QString passphrase;
};
/** @brief Extract the luks passphrases setup.
*
* Given a list of partitions (as set up by the partitioning module,
* so there's maps with keys inside), returns just the list of
* luks passphrases for each device.
*/
static QList< LuksDevice >
getLuksDevices( const QVariantList& list )
{
QList< LuksDevice > luksItems;
for ( const auto& p : list )
{
if ( p.canConvert< QVariantMap >() )
{
LuksDevice d( p.toMap() );
if ( d.isValid )
{
luksItems.append( d );
}
}
}
return luksItems;
}
struct LuksDeviceList
{
LuksDeviceList( const QVariant& partitions )
@ -78,31 +103,6 @@ struct LuksDeviceList
}
}
/** @brief Extract the luks passphrases setup.
*
* Given a list of partitions (as set up by the partitioning module,
* so there's maps with keys inside), returns just the list of
* luks passphrases for each device.
*/
static QList< LuksDevice >
getLuksDevices( const QVariantList& list )
{
QList< LuksDevice > luksItems;
for ( const auto& p : list )
{
if ( p.canConvert< QVariantMap >() )
{
LuksDevice d( p.toMap() );
if ( d.isValid )
{
luksItems.append( d );
}
}
}
return luksItems;
}
QList< LuksDevice > devices;
bool valid;
};
@ -120,6 +120,9 @@ generateTargetKeyfile()
cWarning() << "Could not create LUKS keyfile:" << r.getOutput() << "(exit code" << r.getExitCode() << ')';
return false;
}
// Give ample time to check that the file was created correctly
r = CalamaresUtils::System::instance()->targetEnvCommand( { "ls", "-la", "/" } );
cDebug() << "In target system after creating LUKS file" << r.getOutput();
return true;
}
@ -171,7 +174,8 @@ LuksBootKeyFileJob::exec()
auto it = std::partition( s.devices.begin(), s.devices.end(), []( const LuksDevice& d ) { return d.isRoot; } );
for ( const auto& d : s.devices )
{
cDebug() << Logger::SubEntry << d.isRoot << d.device << d.passphrase;
cDebug() << Logger::SubEntry << ( d.isRoot ? "root" : "dev." ) << d.device << "passphrase?"
<< !d.passphrase.isEmpty();
}
if ( it == s.devices.begin() )

Loading…
Cancel
Save