diff --git a/CHANGES b/CHANGES index 5085ad3c7..723747e44 100644 --- a/CHANGES +++ b/CHANGES @@ -6,11 +6,31 @@ website will have to do for older versions. # 3.2.11 (unreleased) # This release contains contributions from (alphabetically by first name): + - No other contributors this time around. + +This is a security release with no functional changes (except for +improved security) relative to 3.2.10. The Calamares team would like +to acknowledge the help of the following people in reporting and +understanding the issues (alphabetically by first name): + - Kevin Kofler + - Seth Arnold + - Simon Quigley + - Thomas Ward + ## Core ## +No core changes. + ## Modules ## + - *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 + - *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 + # 3.2.10 (2019-06-28) # diff --git a/src/modules/luksbootkeyfile/CMakeLists.txt b/src/modules/luksbootkeyfile/CMakeLists.txt new file mode 100644 index 000000000..be0f0fa28 --- /dev/null +++ b/src/modules/luksbootkeyfile/CMakeLists.txt @@ -0,0 +1,9 @@ +calamares_add_plugin( luksbootkeyfile + TYPE job + EXPORT_MACRO PLUGINDLLEXPORT_PRO + SOURCES + LuksBootKeyFileJob.cpp + LINK_PRIVATE_LIBRARIES + calamares + SHARED_LIB +) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp new file mode 100644 index 000000000..9b49b91cb --- /dev/null +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.cpp @@ -0,0 +1,210 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * 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 . + */ + +#include "LuksBootKeyFileJob.h" + +#include "utils/CalamaresUtilsSystem.h" +#include "utils/Logger.h" +#include "utils/UMask.h" +#include "utils/Variant.h" + +#include "GlobalStorage.h" +#include "JobQueue.h" + +LuksBootKeyFileJob::LuksBootKeyFileJob( QObject* parent ) + : Calamares::CppJob( parent ) +{ +} + +LuksBootKeyFileJob::~LuksBootKeyFileJob() {} + +QString +LuksBootKeyFileJob::prettyName() const +{ + return tr( "Configuring LUKS key file." ); +} + +struct LuksDevice +{ + LuksDevice( const QMap< QString, QVariant >& pinfo ) + : isValid( false ) + , isRoot( false ) + { + if ( pinfo.contains( "luksMapperName" ) ) + { + QString fs = pinfo[ "fs" ].toString(); + QString mountPoint = pinfo[ "mountPoint" ].toString(); + + if ( !mountPoint.isEmpty() || fs == QStringLiteral( "linuxswap" ) ) + { + isValid = true; + isRoot = mountPoint == '/'; + device = pinfo[ "device" ].toString(); + passphrase = pinfo[ "luksPassphrase" ].toString(); + } + } + } + + bool isValid; + bool isRoot; + QString device; + QString passphrase; +}; + +struct LuksDeviceList +{ + LuksDeviceList( const QVariant& partitions ) + : valid( false ) + { + if ( partitions.canConvert< QVariantList >() ) + { + devices = getLuksDevices( partitions.toList() ); + valid = true; + } + } + + /** @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; +}; + +static const char keyfile[] = "/crypto_keyfile.bin"; + +static bool +generateTargetKeyfile() +{ + CalamaresUtils::UMask m( CalamaresUtils::UMask::Safe ); + auto r = CalamaresUtils::System::instance()->targetEnvCommand( + { "dd", "bs=512", "count=4", "if=/dev/urandom", QString( "of=%1" ).arg( keyfile ) } ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Could not create LUKS keyfile:" << r.getOutput() << "(exit code" << r.getExitCode() << ')'; + return false; + } + return true; +} + +static bool +setupLuks( const LuksDevice& d ) +{ + auto r = CalamaresUtils::System::instance()->targetEnvCommand( + { "cryptsetup", "luksAddKey", d.device, keyfile }, QString(), d.passphrase, 15 ); + if ( r.getExitCode() != 0 ) + { + cWarning() << "Could not configure LUKS keyfile on" << d.device << ':' << r.getOutput() << "(exit code" + << r.getExitCode() << ')'; + return false; + } + return true; +} + +Calamares::JobResult +LuksBootKeyFileJob::exec() +{ + const auto* gs = Calamares::JobQueue::instance()->globalStorage(); + if ( !gs ) + { + return Calamares::JobResult::internalError( + "LukeBootKeyFile", "No GlobalStorage defined.", Calamares::JobResult::InvalidConfiguration ); + } + if ( !gs->contains( "partitions" ) ) + { + cError() << "No GS[partitions] key."; + return Calamares::JobResult::internalError( + "LukeBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration ); + } + + LuksDeviceList s( gs->value( "partitions" ) ); + if ( !s.valid ) + { + cError() << "GS[partitions] is invalid"; + return Calamares::JobResult::internalError( + "LukeBootKeyFile", tr( "No partitions are defined." ), Calamares::JobResult::InvalidConfiguration ); + } + + cDebug() << "There are" << s.devices.count() << "LUKS partitions"; + if ( s.devices.count() < 1 ) + { + cDebug() << Logger::SubEntry << "Nothing to do for LUKS."; + return Calamares::JobResult::ok(); + } + + 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; + } + + if ( it == s.devices.begin() ) + { + // Then there was no root partition + cDebug() << Logger::SubEntry << "No root partition."; + return Calamares::JobResult::ok(); + } + + if ( s.devices.first().passphrase.isEmpty() ) + { + cDebug() << Logger::SubEntry << "No root passphrase."; + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Root partition %1 is LUKS but no passphrase has been set." ).arg( s.devices.first().device ) ); + } + + if ( !generateTargetKeyfile() ) + { + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Could not create LUKS key file for root partition %1." ).arg( s.devices.first().device ) ); + } + + for ( const auto& d : s.devices ) + { + if ( !setupLuks( d ) ) + return Calamares::JobResult::error( + tr( "Encrypted rootfs setup error" ), + tr( "Could configure LUKS key file on partition %1." ).arg( d.device ) ); + } + + return Calamares::JobResult::ok(); +} + +CALAMARES_PLUGIN_FACTORY_DEFINITION( LuksBootKeyFileJobFactory, registerPlugin< LuksBootKeyFileJob >(); ) diff --git a/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h new file mode 100644 index 000000000..2d4d6d319 --- /dev/null +++ b/src/modules/luksbootkeyfile/LuksBootKeyFileJob.h @@ -0,0 +1,48 @@ +/* === This file is part of Calamares - === + * + * Copyright 2019, Adriaan de Groot + * + * 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 . + */ + +#ifndef LUKSBOOTKEYFILEJOB_H +#define LUKSBOOTKEYFILEJOB_H + +#include "CppJob.h" +#include "PluginDllMacro.h" +#include "utils/PluginFactory.h" + +#include +#include + +/** @brief Creates the LUKS boot key file and adds it to the cryptsetup. + * + * This job has no configuration, because it takes everything + * from the global storage settings set by others. + */ +class PLUGINDLLEXPORT LuksBootKeyFileJob : public Calamares::CppJob +{ + Q_OBJECT +public: + explicit LuksBootKeyFileJob( QObject* parent = nullptr ); + virtual ~LuksBootKeyFileJob() override; + + QString prettyName() const override; + + Calamares::JobResult exec() override; +}; + +CALAMARES_PLUGIN_FACTORY_DECLARATION( LuksBootKeyFileJobFactory ) + +#endif // LUKSBOOTKEYFILEJOB_H diff --git a/src/modules/luksbootkeyfile/main.py b/src/modules/luksbootkeyfile/main.py deleted file mode 100644 index fb0146cf8..000000000 --- a/src/modules/luksbootkeyfile/main.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -# -# === This file is part of Calamares - === -# -# Copyright 2016, Teo Mrnjavac -# Copyright 2017, Alf Gaida -# Copyright 2017, 2019, Adriaan de Groot -# -# 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 . - -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 _("Configuring LUKS key file.") - - -def run(): - """ - This module sets up a file crypto_keyfile.bin on the rootfs, assuming the - rootfs is LUKS encrypted and a passphrase is provided. This file is then - included in the initramfs and used for unlocking the rootfs from a - previously unlocked GRUB2 session. - :return: - """ - - partitions = libcalamares.globalstorage.value("partitions") - - if not partitions: - libcalamares.utils.warning("partitions is empty, {!s}".format(partitions)) - return (_("Configuration Error"), - _("No partitions are defined for
{!s}
to use." ).format("luksbootkey")) - - luks_root_device = "" - luks_root_passphrase = "" - - additional_luks_devices = [] - - for partition in partitions: - if partition["mountPoint"] == "/" and "luksMapperName" in partition: - luks_root_device = partition["device"] - luks_root_passphrase = partition["luksPassphrase"] - elif "luksMapperName" in partition and\ - (partition["mountPoint"] or partition["fs"] == "linuxswap"): - additional_luks_devices.append((partition["device"], - partition["luksPassphrase"])) - - if not luks_root_device: - return None - - if not luks_root_passphrase: - libcalamares.utils.debug("No LUKS passphrase, root {!s}".format(luks_root_device)) - return ( - _("Encrypted rootfs setup error"), - _("Rootfs partition {!s} is LUKS but no passphrase found.").format(luks_root_device)) - - # Generate random keyfile - check_target_env_call(["dd", - "bs=512", - "count=4", - "if=/dev/urandom", - "of=/crypto_keyfile.bin"]) - - check_target_env_call(["cryptsetup", - "luksAddKey", - luks_root_device, - "/crypto_keyfile.bin"], - luks_root_passphrase, - 15) # timeout 15s - - for additional_device in additional_luks_devices: - check_target_env_call(["cryptsetup", - "luksAddKey", - additional_device[0], - "/crypto_keyfile.bin"], - additional_device[1], - 15) # timeout 15s - - check_target_env_call(["chmod", - "g-rwx,o-rwx", - "/crypto_keyfile.bin"]) - - return None diff --git a/src/modules/luksbootkeyfile/module.desc b/src/modules/luksbootkeyfile/module.desc deleted file mode 100644 index 11a0173d5..000000000 --- a/src/modules/luksbootkeyfile/module.desc +++ /dev/null @@ -1,5 +0,0 @@ ---- -type: "job" -name: "luksbootkeyfile" -interface: "python" -script: "main.py"