diff options
Diffstat (limited to '')
-rw-r--r-- | debian/scripts/cryptdisks_start | 63 | ||||
-rw-r--r-- | debian/scripts/cryptdisks_stop | 38 | ||||
-rw-r--r-- | debian/scripts/decrypt_derived | 32 | ||||
-rw-r--r-- | debian/scripts/decrypt_gnupg | 26 | ||||
-rw-r--r-- | debian/scripts/decrypt_gnupg-sc | 44 | ||||
-rw-r--r-- | debian/scripts/decrypt_keyctl | 55 | ||||
-rw-r--r-- | debian/scripts/decrypt_opensc | 46 | ||||
-rw-r--r-- | debian/scripts/decrypt_ssl | 17 | ||||
-rw-r--r-- | debian/scripts/gen-ssl-key | 22 | ||||
-rw-r--r-- | debian/scripts/luksformat | 133 | ||||
-rw-r--r-- | debian/scripts/passdev.c | 286 | ||||
-rw-r--r-- | debian/scripts/po/Makefile | 39 | ||||
-rw-r--r-- | debian/scripts/po/de.po | 76 | ||||
-rw-r--r-- | debian/scripts/po/luksformat.pot | 69 | ||||
-rw-r--r-- | debian/scripts/suspend/cryptsetup-suspend-wrapper | 320 | ||||
-rw-r--r-- | debian/scripts/suspend/cryptsetup-suspend.c | 225 | ||||
-rw-r--r-- | debian/scripts/suspend/cryptsetup-suspend.shutdown | 3 | ||||
-rw-r--r-- | debian/scripts/suspend/suspend.conf | 10 | ||||
-rw-r--r-- | debian/scripts/suspend/systemd/cryptsetup-suspend.conf | 12 |
19 files changed, 1516 insertions, 0 deletions
diff --git a/debian/scripts/cryptdisks_start b/debian/scripts/cryptdisks_start new file mode 100644 index 0000000..623423f --- /dev/null +++ b/debian/scripts/cryptdisks_start @@ -0,0 +1,63 @@ +#!/bin/sh + +# cryptdisks_start - wrapper around cryptsetup which parses +# /etc/crypttab, just like mount parses /etc/fstab. + +# Initial code and (c) 2007 Jon Dowland <jon@alcopop.org> +# License: GNU General Public License, v2 or any later +# (https://www.gnu.org/copyleft/gpl.html) + +set -e + +. /lib/cryptsetup/cryptdisks-functions + +INITSTATE="manual" +DEFAULT_LOUD="yes" +FORCE_START="yes" + +usage() { + local rv="${1:-1}" + echo "Usage: $0 [-r|--readonly] <name> [.. <name>]" >&2 + echo >&2 + echo "reads $TABFILE and starts the mapping corresponding to <name>" >&2 + exit $rv +} + +CRYPTTAB_EXTRA_OPTIONS= +while [ $# -gt 0 ]; do + case "$1" in + -r|--readonly) CRYPTTAB_EXTRA_OPTIONS="${CRYPTTAB_EXTRA_OPTIONS:+$CRYPTTAB_EXTRA_OPTIONS,}readonly";; + -h|--help|-\?) usage 0;; + --) shift; break;; + -*) echo "Error: unknown option '$1'" >&2; usage 1;; + *) break;; + esac + shift +done +[ $# -gt 0 ] || usage 1 + +if [ $(id -u) -ne 0 ]; then + log_warning_msg "$0 needs root privileges" + exit 1 +fi + +log_action_begin_msg "Starting crypto disk" +mount_fs + +rv=0 +for name in "$@"; do + if ! crypttab_find_entry --quiet "$name"; then + device_msg "$name" "failed, not found in crypttab" + rv=1 + else + if [ -n "$CRYPTTAB_EXTRA_OPTIONS" ]; then + CRYPTTAB_OPTIONS="$CRYPTTAB_OPTIONS,$CRYPTTAB_EXTRA_OPTIONS" + _CRYPTTAB_OPTIONS="$_CRYPTTAB_OPTIONS,$CRYPTTAB_EXTRA_OPTIONS" + fi + setup_mapping || rv=$? + fi +done +umount_fs + +log_action_end_msg $rv +exit $rv diff --git a/debian/scripts/cryptdisks_stop b/debian/scripts/cryptdisks_stop new file mode 100644 index 0000000..ea0faaf --- /dev/null +++ b/debian/scripts/cryptdisks_stop @@ -0,0 +1,38 @@ +#!/bin/sh + +# cryptdisks_stop - wrapper around cryptsetup which parses +# /etc/crypttab, just like mount parses /etc/fstab. + +# Initial code stolen from cryptdisks_start by Jon Dowland <jon@alcopop.org> +# Copyright (C) 2008 by Jonas Meurer <jonas@freesources.org> +# License: GNU General Public License, v2 or any later +# (https://www.gnu.org/copyleft/gpl.html) + +set -e + +if [ $# -lt 1 ]; then + echo "usage: $0 <name>" >&2 + echo >&2 + echo "reads /etc/crypttab and stops the mapping corresponding to <name>" >&2 + exit 1 +fi + +. /lib/cryptsetup/cryptdisks-functions + +INITSTATE="manual" +DEFAULT_LOUD="yes" + +if [ $(id -u) -ne 0 ]; then + log_warning_msg "$0 needs root privileges" + exit 1 +fi + +log_action_begin_msg "Stopping crypto disk" + +rv=0 +for name in "$@"; do + remove_mapping "$name" || rv=$? +done + +log_action_end_msg $rv +exit $rv diff --git a/debian/scripts/decrypt_derived b/debian/scripts/decrypt_derived new file mode 100644 index 0000000..0e1e418 --- /dev/null +++ b/debian/scripts/decrypt_derived @@ -0,0 +1,32 @@ +#!/bin/sh + +# WARNING: If you use the decrypt_derived keyscript for devices with +# persistent data (i.e. not swap or temp devices), then you will lose +# access to that data permanently if something damages the LUKS header +# of the LUKS device you derive from. The same applies if you luksFormat +# the device, even if you use the same passphrase(s). A LUKS header +# backup, or better a backup of the data on the derived device may be +# a good idea. See the Cryptsetup FAQ on how to do this right. + +if [ -z "$1" ]; then + echo "$0: must be executed with a crypto device as argument" >&2 + exit 1 +fi + +unset -v keys count +keys="$(dmsetup table --target crypt --showkeys -- "$1" 2>/dev/null | cut -s -d' ' -f5)" +count="$(printf '%s' "$keys" | wc -l)" + +if [ -n "$keys" ] && [ $count -le 1 ]; then + if [ "${keys#:}" = "$keys" ]; then + printf '%s' "$keys" + exit 0 + else + echo "$0: device $1 uses the kernel keyring" >&2 + fi +elif [ $count -eq 0 ]; then + echo "$0: device $1 doesn't exist or isn't a crypto device" >&2 +else + echo "$0: more than one device match" >&2 +fi +exit 1 diff --git a/debian/scripts/decrypt_gnupg b/debian/scripts/decrypt_gnupg new file mode 100644 index 0000000..18ab575 --- /dev/null +++ b/debian/scripts/decrypt_gnupg @@ -0,0 +1,26 @@ +#!/bin/sh + +decrypt_gpg () { + echo "Performing GPG symmetric decryption ..." >&2 + if ! /lib/cryptsetup/askpass "Enter passphrase for key $1: " | \ + /usr/bin/gpg -q --batch --no-options \ + --no-random-seed-file --no-default-keyring \ + --keyring /dev/null --secret-keyring /dev/null \ + --trustdb-name /dev/null --passphrase-fd 0 --decrypt -- "$1"; then + return 1 + fi + return 0 +} + +if [ ! -x /usr/bin/gpg ]; then + echo "$0: /usr/bin/gpg is not available" >&2 + exit 1 +fi + +if [ -z "$1" ]; then + echo "$0: missing key as argument" >&2 + exit 1 +fi + +decrypt_gpg "$1" +exit $? diff --git a/debian/scripts/decrypt_gnupg-sc b/debian/scripts/decrypt_gnupg-sc new file mode 100644 index 0000000..84eb62c --- /dev/null +++ b/debian/scripts/decrypt_gnupg-sc @@ -0,0 +1,44 @@ +#!/bin/sh + +if [ -d "/cryptroot/gnupghome" ]; then + export GNUPGHOME="/cryptroot/gnupghome" +fi + +run_gpg() { + gpg --no-options --trust-model=always "$@" +} +decrypt_gpg () { + local console _ + if ! GPG_TTY="$(tty)"; then + read console _ </proc/consoles + GPG_TTY="/dev/$console" + fi + export GPG_TTY + + if ! run_gpg --decrypt -- "$1"; then + return 1 + fi + return 0 +} + +# `gpg-connect-agent LEARN /bye` is another (lighter) way, but it's +# harder to retrieve the return code +if ! run_gpg --batch --quiet --no-tty --card-status >/dev/null; then + echo "Please insert OpenPGP SmartCard..." >&2 + until run_gpg --batch --quiet --no-tty --card-status; do + sleep 1 + done >/dev/null 2>&1 +fi + +if [ ! -x /usr/bin/gpg ]; then + echo "$0: /usr/bin/gpg is not available" >&2 + exit 1 +fi + +if [ -z "$1" ] || [ ! -f "$1" ]; then + echo "$0: missing key as argument" >&2 + exit 1 +fi + +decrypt_gpg "$1" +exit $? diff --git a/debian/scripts/decrypt_keyctl b/debian/scripts/decrypt_keyctl new file mode 100644 index 0000000..6032db0 --- /dev/null +++ b/debian/scripts/decrypt_keyctl @@ -0,0 +1,55 @@ +#!/bin/sh +# decrypt_keyctl - to use in /etc/crypttab as keyscript +# Allows to cache passwords for cryptdevices for 60s +# The same password is used for for cryptdevices with the same identifier. +# The keyfile parameter, which is the third field from /etc/crypttab, is +# used as identifier in this keyscript. +# +# sample crypttab entries: +# test1 /dev/sda1 test_pw luks,keyscript=decrypt_keyctl +# test2 /dev/sda2 test_pw luks,keyscript=decrypt_keyctl +# test3 /dev/sda3 test_other_pw luks,keyscript=decrypt_keyctl +# +# test1 and test2 have the same identifier thus test2 does not need a password +# typed in manually + +die() +{ + echo "$@" >&2 + exit 1 +} + +if [ -z "${CRYPTTAB_KEY:-}" ] || [ "$CRYPTTAB_KEY" = "none" ]; then + # store the passphrase in the key name used by systemd-ask-password + ID_="cryptsetup" +else + # the keyfile given from crypttab is used as identifier in the keyring + # including the prefix "cryptsetup:" + ID_="cryptsetup:$CRYPTTAB_KEY" +fi +TIMEOUT_='60' +ASKPASS_='/lib/cryptsetup/askpass' +PROMPT_="Caching passphrase for ${CRYPTTAB_NAME}: " + + +if ! KID_="$(keyctl search @u user "$ID_" 2>/dev/null)" || \ + [ -z "$KID_" ] || [ "$CRYPTTAB_TRIED" -gt 0 ]; then + # key not found or wrong, ask the user + KEY_="$($ASKPASS_ "$PROMPT_")" || die "Error executing $ASKPASS_" + if [ -n "$KID_" ]; then + # I have cached wrong password and now i may use either `keyctl update` + # to update $KID_ or just unlink old key, and add new. With `update` i + # may hit "Key has expired", though. So i'll go "unlink and add" way. + keyctl unlink "$KID_" @u + KID_="" + fi + KID_="$(printf "%s" "$KEY_" | keyctl padd user "$ID_" @u)" + [ -n "$KID_" ] || die "Error adding passphrase to kernel keyring" + if ! keyctl timeout "$KID_" "$TIMEOUT_"; then + keyctl unlink "$KID_" @u + die "Error setting timeout on key ($KID_), removing" + fi +else + echo "Using cached passphrase for ${CRYPTTAB_NAME}." >&2 +fi +keyctl pipe "$KID_" diff --git a/debian/scripts/decrypt_opensc b/debian/scripts/decrypt_opensc new file mode 100644 index 0000000..b06fc98 --- /dev/null +++ b/debian/scripts/decrypt_opensc @@ -0,0 +1,46 @@ +#!/bin/sh + +# Why not use "openct-tool rwait" instead of polling opensc-tool exit status? +# Well openct daemon has to be running which interferes with pcscd since both +# implement reader drivers, my particular CCID reader (SCM SCR331-LC1) doesn't +# work with the CCID driver in openct, however it does work with pcscd. + +# Why not use "opensc-tool --wait" instead of polling opensc-tool exit status? +# Although opensc-tool --help reports that there is a --wait option, it doesn't +# seem to be implemented. + +check_card() { + cardfound=0 + + if /usr/bin/opensc-tool -n >/dev/null 2>&1; then + cardfound=1 + fi +} + +wait_card() { + check_card + if [ $cardfound = 0 ] ; then + echo "Waiting for Smart Card..." >&2 + tries=0 + while [ $cardfound = 0 ] && [ $tries -lt 60 ] ; do + sleep 1 + check_card + tries=$(($tries + 1)) + done + if [ $cardfound = 0 ] ; then + echo 'Failed to find Smart Card card!' >&2 + exit 1 + fi + fi +} + +wait_card +if [ -x /bin/plymouth ] && plymouth --ping; then + # Get pin number from plymouth + /usr/bin/pkcs15-crypt --decipher --input "$1" --pkcs1 --raw \ + --pin "$(plymouth ask-for-password --prompt "Enter pin for $CRYPTTAB_NAME: ")" +else + # Get pin number from console + /usr/bin/pkcs15-crypt --decipher --input "$1" --pkcs1 --raw </dev/console 2>/dev/console +fi +exit $? diff --git a/debian/scripts/decrypt_ssl b/debian/scripts/decrypt_ssl new file mode 100644 index 0000000..6664001 --- /dev/null +++ b/debian/scripts/decrypt_ssl @@ -0,0 +1,17 @@ +#!/bin/sh +# +# Script to decrypt the key which is encrypted with openssl. +# See /usr/share/doc/cryptsetup/examples/gen-ssl-key to create such a key. +# + +decrypt_ssl () { + echo "" >&2 + echo "Decrypting ssl key $1..." >&2 + if ! /usr/bin/openssl enc -aes-256-cbc -d -salt -in "$1" 2>/dev/null; then + return 1 + fi + return 0 +} + +decrypt_ssl "$1" +exit $? diff --git a/debian/scripts/gen-ssl-key b/debian/scripts/gen-ssl-key new file mode 100644 index 0000000..70a6fb3 --- /dev/null +++ b/debian/scripts/gen-ssl-key @@ -0,0 +1,22 @@ +#!/bin/sh +# +# script to generate a keyfile that is encrypted with openssl +# +# Written 2005 by Markus Nass <generalstone@gmx.net> +# Improved 2006 by Jonas Meurer <jonas@freesources.org> +# Further improved 2006 by Markus Nass <generalstone@gmx.net> + +usage() { + echo "Usage: $0 <key>" + exit 1 +} + +if [ -z "${1-}" ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then + usage +fi + +if [ -x /usr/bin/openssl ]; then + dd if=/dev/random bs=1c count=256 | openssl enc -aes-256-cbc -e -salt >"$1" +else + echo "/usr/bin/openssl is not available" && exit 1 +fi diff --git a/debian/scripts/luksformat b/debian/scripts/luksformat new file mode 100644 index 0000000..ae17f79 --- /dev/null +++ b/debian/scripts/luksformat @@ -0,0 +1,133 @@ +#!/usr/bin/perl -w + +# luksformat - wrapper around LUKS-capable cryptsetup and mkfs for easy +# creation of an encrypted device. +# +# (C) 2005 Canonical Ltd. +# Author: Martin Pitt <martin.pitt@ubuntu.com> +# License: GNU General Public License, v2 or any later +# (https://www.gnu.org/copyleft/gpl.html) + +use Getopt::Long qw(:config pass_through); + +BEGIN { + eval 'use Locale::gettext'; + if ($@) { + *gettext = sub { shift }; + *textdomain = sub { "" }; + *LC_MESSAGES = sub { 5 }; + } + eval { + require POSIX; + import POSIX qw(setlocale); + }; + if ($@) { + *setlocale = sub { return 1 }; + } +} + +setlocale(LC_MESSAGES, ""); +textdomain("luksformat"); + +if ($> != 0) { + print STDERR gettext("This program needs to be started as root\n"); + exit 1; +} + +sub usage() { + print gettext("luksformat - Create and format an encrypted LUKS device +Usage: luksformat [-t <file system>] <device> [ mkfs options ]\n\n"); + exit 1; +} + +# default file system +$fs = 'vfat'; +exit 1 unless GetOptions ('t|type=s' => \$fs); + +GetOptions ('help', \$help); +if (($#ARGV < 0) || ($help)) { + usage(); +} + +$device = shift(@ARGV); + +open(MOUNTS, "/proc/mounts"); +while (<MOUNTS>) { + die sprintf(gettext("Error: device mounted: %s\n"), $device) if (/\Q$device\E/) +} + +if (-x "/usr/sbin/mkfs.$fs") { + $mkfs = "/usr/sbin/mkfs.$fs"; +} +elsif (-x "/usr/bin/mkfs.$fs") { + $mkfs = "/usr/bin/mkfs.$fs"; +} +elsif (-x "/sbin/mkfs.$fs") { + $mkfs = "/sbin/mkfs.$fs"; +} +elsif (-x "/bin/mkfs.$fs") { + $mkfs = "/bin/mkfs.$fs"; +} +else { + printf STDERR (gettext("Error: invalid file system: %s\n"), $fs); + exit 1; +} + +# generate temporary mapped device name which is not yet used +$name = ""; +for ($i = 1; $i < 100; $i++) { + if (! -e "/dev/mapper/luksformat$i") { + $name = "luksformat$i"; + last; + } +} + +$name or die sprintf(gettext("Error: could not generate temporary mapped device name")); + +# we do not need to be overly concerned with race conditions here, cryptsetup +# will just fail if the name already exists now. +printf (gettext("Creating encrypted device on %s...\n"), $device); +if ((system 'cryptsetup', 'luksFormat', $device)) { + die sprintf(gettext("Could not create LUKS device %s"), $device); +} + +print gettext("Please enter your passphrase again to verify it\n"); +if ((system 'cryptsetup', 'open', '--type', 'luks', $device, $name) != 0) { + print STDERR gettext("The passphrases you entered were not identical\n"); + exit 1; +} + +$result = system $mkfs, "/dev/mapper/$name", @ARGV; +print "\n"; +system 'udevadm', 'settle', '--timeout=30'; +system 'cryptsetup', 'luksClose', $name; + +die sprintf(gettext("Could not format device with file system %s"), $fs) if $result; + +__END__ + +=head1 NAME + +luksformat - Create and format an encrypted LUKS device + +=head1 SYNOPSIS + +B<luksformat> [B<-t> I<fstype>] I<device> [ mkfs options ] + +=head1 DESCRIPTION + +B<luksformat> is a wrapper around B<cryptsetup> and B<mkfs> which provides an +easy interface for creating an encrypted device that follows the LUKS standard +and for putting a file system onto the encrypted device. + +The default file system is B<vfat> since that is most commonly used on +removable devices. However, you can specify any available file system with the +B<-t> option. + +=head1 SEE ALSO + +L<cryptsetup(8)>, L<mkfs(8)> + +=head1 AUTHOR + +This program was written by Martin Pitt <martin.pitt@ubuntu.com>. diff --git a/debian/scripts/passdev.c b/debian/scripts/passdev.c new file mode 100644 index 0000000..845ccae --- /dev/null +++ b/debian/scripts/passdev.c @@ -0,0 +1,286 @@ +/* + * passdev.c - waits for a given device to appear, mounts it and reads a + * key from it which is piped to stdout. + * + * Copyright (C) 2008 David Härdeman <david@hardeman.nu> + * + * This package 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 package 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 package; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#define _DEFAULT_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <stdbool.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <string.h> +#include <fcntl.h> +#include <sys/mount.h> + +static bool do_debug = false; + +static void +debug(const char *fmt, ...) +{ + va_list ap; + + if (!do_debug) + return; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +static bool +do_mount(const char *device, const char *dir) +{ + pid_t pid; + int status; + char *fstypes[] = { "ext4", "ext3", "ext2", "vfat", "btrfs", "reiserfs", "xfs", "jfs", "ntfs", "iso9660", "udf" }; + int fsindex; + + if (!device || !dir) + return false; + + for (fsindex = 0; + fsindex < (sizeof(fstypes) / sizeof(fstypes[0])); + fsindex++) + { + pid = fork(); + if (pid < 0) { + /* Error */ + return false; + } else if (pid > 0) { + /* We're in the parent process */ + do { + waitpid(pid, &status, 0); + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) + return true; + + /* Let's try another fstype */ + continue; + } else { + /* We're in the child process */ + debug("Mounting %s at %s\n", device, dir); + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + open("/dev/null", O_RDONLY, 0); + open("/dev/null", O_WRONLY, 0); + open("/dev/null", O_WRONLY, 0); + execl("/bin/mount", "/bin/mount", "-n", "-t", + fstypes[fsindex], + /*"ext4,ext3,ext2,vfat,btrfs,reiserfs,xfs,jfs,ntfs,iso9660,udf",*/ + "-o", "noatime,nodiratime,nodev,noexec,nosuid,ro", + device, dir, (char *)NULL); + + /* If execl works, we won't end up here */ + exit(EXIT_FAILURE); + } + } + + /* We've tried all fstypes with no luck */ + return false; +} + +int +main(int argc, char **argv, char **envp) +{ + char *debugval; + char *devpath; + char *filepath; + struct stat st; + char *tmppath; + char tpath[] = "/tmp/passdev.XXXXXX"; + char *keypath; + int fd; + size_t toread; + size_t bytesread; + char *keybuffer; + size_t towrite; + size_t byteswritten; + ssize_t bytes; + char *to; + int timeout = 0; + bool do_timeout = false; + + /* We only take one argument */ + if (argc != 2) { + fprintf(stderr, "Incorrect number of arguments\n"); + goto error; + } + + /* If DEBUG=1 is in the environment, enable debug messages */ + debugval = getenv("DEBUG"); + if (debugval && atoi(debugval) > 0) + do_debug = true; + + /* Split string into device and path (and timeout) */ + devpath = argv[1]; + filepath = strchr(devpath, ':'); + if (!filepath || !(*filepath) || !(*(filepath + 1))) { + fprintf(stderr, "Invalid key path\n"); + goto error; + } + *filepath = '\0'; + filepath++; + to = strchr(filepath, ':'); + if (to && (*to) && (*(to + 1))) { + *to = '\0'; + to++; + timeout = atoi(to); + if (timeout > 0) + do_timeout = true; + } + debug("Path is %p and filepath is %p\n", devpath, filepath); + if (do_timeout) + debug("Timeout is %i\n",timeout); + + /* Wait until device is available */ + if (access(devpath, F_OK)) { + debug("Waiting for %s\n", devpath); + while(access(devpath, F_OK)) { + sleep(1); + if (do_timeout) { + if (timeout <= 0) + break; + timeout--; + } + } + } + + /* Make sure device is a blockdev */ + if (stat(devpath, &st)) { + fprintf(stderr, "Unable to stat %s\n", devpath); + goto error; + } else if (!S_ISBLK(st.st_mode)) { + fprintf(stderr, "%s is no block device\n", devpath); + goto error; + } + + /* Create a tmp dir where we mount the device */ + tmppath = mkdtemp(tpath); + if (!tmppath) { + fprintf(stderr, "Failed to create temporary directory\n"); + goto error; + } + + /* Ok, mount it */ + if (!do_mount(devpath, tmppath)) { + fprintf(stderr, "Failed to mount %s\n", devpath); + goto error_rmdir; + } + + /* Generate the full path to the keyfile */ + keypath = malloc(strlen(tmppath) + 1 + strlen(filepath) + 1); + if (!keypath) { + fprintf(stderr, "Failed to allocate memory\n"); + goto error_umount; + } + sprintf(keypath, "%s/%s", tmppath, filepath); + + /* Check that the keyfile exists */ + if (access(keypath, F_OK)) { + fprintf(stderr, "Keyfile doesn't exist\n"); + goto error_free; + } + + /* Get the size of the keyfile */ + if (stat(keypath, &st)) { + fprintf(stderr, "Unable to stat keyfile\n"); + goto error_free; + } + + /* Check the size of the keyfile */ + if (st.st_size < 0) { + fprintf(stderr, "Invalid keyfile size\n"); + goto error_free; + } + toread = (size_t)st.st_size; + + /* Open the keyfile */ + if ((fd = open(keypath, O_RDONLY)) < 0) { + fprintf(stderr, "Failed to open keyfile\n"); + goto error_free; + } + + /* Allocate a buffer for the keyfile contents */ + keybuffer = malloc(toread); + if (!keybuffer) { + fprintf(stderr, "Failed to allocate memory\n"); + goto error_close; + exit(EXIT_FAILURE); + } + + /* Read the keyfile */ + bytesread = 0; + while (bytesread < toread) { + bytes = read(fd, keybuffer + bytesread, toread - bytesread); + if (bytes <= 0) { + fprintf(stderr, "Failed to read entire key\n"); + goto error_keybuffer; + } + bytesread += bytes; + } + + /* Clean up */ + close(fd); + free(keypath); + umount(tmppath); + rmdir(tmppath); + + /* Write result */ + byteswritten = 0; + towrite = toread; + while (byteswritten < towrite) { + bytes = write(STDOUT_FILENO, keybuffer + byteswritten, + towrite - byteswritten); + if (bytes <= 0) { + fprintf(stderr, "Failed to write entire key\n"); + memset(keybuffer, 0, toread); + free(keybuffer); + goto error; + } + byteswritten += bytes; + } + + /* Clean up */ + memset(keybuffer, 0, toread); + free(keybuffer); + + /* Done */ + exit(EXIT_SUCCESS); + + /* Error handling */ +error_keybuffer: + memset(keybuffer, 0, toread); + free(keybuffer); +error_close: + close(fd); +error_free: + free(keypath); +error_umount: + umount(tmppath); +error_rmdir: + rmdir(tmppath); +error: + exit(EXIT_FAILURE); +} + diff --git a/debian/scripts/po/Makefile b/debian/scripts/po/Makefile new file mode 100644 index 0000000..9eb8acf --- /dev/null +++ b/debian/scripts/po/Makefile @@ -0,0 +1,39 @@ +XGETTEXT = xgettext +MSGFMT = msgfmt +MSGMERGE = msgmerge + +LOCALEDIR = /usr/share/locale + +.SUFFIXES: .po .mo .pot + +%.mo: %.po + $(MSGFMT) -o $@ $< + +PO = $(wildcard *.po) +LANG = $(basename $(PO)) +MO = $(addsuffix .mo,$(LANG)) +SOURCES = ../luksformat + +all: update $(MO) +update: luksformat.pot + -@for po in $(PO); do \ + echo -n "Updating $$po"; \ + $(MSGMERGE) -U $$po luksformat.pot; \ + done; + +luksformat.pot: $(SOURCES) + $(XGETTEXT) -c -L Perl -kgtx \ + --msgid-bugs-address=pkg-cryptsetup-devel@alioth-lists.debian.net \ + -o $@ $(SOURCES) + +install: all + for i in $(MO) ; do \ + t=$(DESTDIR)/$(LOCALEDIR)/`basename $$i .mo`/LC_MESSAGES ;\ + install -d $$t ;\ + install -m 644 $$i $$t/luksformat.mo ;\ + done + +clean: + $(RM) $(MO) *~ + +.PHONY: update diff --git a/debian/scripts/po/de.po b/debian/scripts/po/de.po new file mode 100644 index 0000000..76c7f2f --- /dev/null +++ b/debian/scripts/po/de.po @@ -0,0 +1,76 @@ +# German translations for cryptsetup package +# German messages for luksformat in cryptsetup. +# Copyright (C) 2011 THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the cryptsetup package. +# Jonas Meurer <jonas@freesources.org>, 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: cryptsetup 2:1.3.0-1\n" +"Report-Msgid-Bugs-To: pkg-cryptsetup-devel@alioth-lists.debian.net\n" +"POT-Creation-Date: 2015-12-09 13:09+0100\n" +"PO-Revision-Date: 2011-03-08 19:40+0100\n" +"Last-Translator: Jonas Meurer <jonas@freesources.org>\n" +"Language-Team: German\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: ../luksformat:33 +msgid "This program needs to be started as root\n" +msgstr "Dieses Programm muss als Benutzer root gestartet werden\n" + +#: ../luksformat:38 +msgid "" +"luksformat - Create and format an encrypted LUKS device\n" +"Usage: luksformat [-t <file system>] <device> [ mkfs options ]\n" +"\n" +msgstr "" +"luksformat - LUKS-verschlüsselte Partition erstellen und formatieren\n" +"Verwendung: luksformat [-t <Dateisystem>] <Partition> [ mkfs Optionen ]\n" +"\n" + +#: ../luksformat:56 +#, perl-format +msgid "Error: device mounted: %s\n" +msgstr "Fehler: Partition ist eingebunden: %s\n" + +#: ../luksformat:72 +#, perl-format +msgid "Error: invalid file system: %s\n" +msgstr "Fehler: Ungültiges Dateisystem: %s\n" + +#: ../luksformat:85 +#, perl-format +msgid "Error: could not generate temporary mapped device name" +msgstr "Fehler: Erstellen einer temporären Partition schlug fehl" + +#. we do not need to be overly concerned with race conditions here, cryptsetup +#. will just fail if the name already exists now. +#: ../luksformat:89 +#, perl-format +msgid "Creating encrypted device on %s...\n" +msgstr "Erstelle verschlüsselte Partition auf %s...\n" + +#: ../luksformat:91 +#, perl-format +msgid "Could not create LUKS device %s" +msgstr "Erstellen der LUKS-Partition %s schlug fehl" + +#: ../luksformat:94 +msgid "Please enter your passphrase again to verify it\n" +msgstr "Bitte zum verifizieren das Passwort erneut eingeben\n" + +#: ../luksformat:96 +msgid "The passphrases you entered were not identical\n" +msgstr "Die eingegebenen Passwörter waren nicht identisch\n" + +#: ../luksformat:105 +#, perl-format +msgid "Could not format device with file system %s" +msgstr "Formatieren der Partition mit dem Dateisystem %s schlug fehl" + +#~ msgid "%s: %s" +#~ msgstr "%s: %s" diff --git a/debian/scripts/po/luksformat.pot b/debian/scripts/po/luksformat.pot new file mode 100644 index 0000000..f6c1e56 --- /dev/null +++ b/debian/scripts/po/luksformat.pot @@ -0,0 +1,69 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: pkg-cryptsetup-devel@alioth-lists.debian.net\n" +"POT-Creation-Date: 2015-12-09 13:09+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: ../luksformat:33 +msgid "This program needs to be started as root\n" +msgstr "" + +#: ../luksformat:38 +msgid "" +"luksformat - Create and format an encrypted LUKS device\n" +"Usage: luksformat [-t <file system>] <device> [ mkfs options ]\n" +"\n" +msgstr "" + +#: ../luksformat:56 +#, perl-format +msgid "Error: device mounted: %s\n" +msgstr "" + +#: ../luksformat:72 +#, perl-format +msgid "Error: invalid file system: %s\n" +msgstr "" + +#: ../luksformat:85 +#, perl-format +msgid "Error: could not generate temporary mapped device name" +msgstr "" + +#. we do not need to be overly concerned with race conditions here, cryptsetup +#. will just fail if the name already exists now. +#: ../luksformat:89 +#, perl-format +msgid "Creating encrypted device on %s...\n" +msgstr "" + +#: ../luksformat:91 +#, perl-format +msgid "Could not create LUKS device %s" +msgstr "" + +#: ../luksformat:94 +msgid "Please enter your passphrase again to verify it\n" +msgstr "" + +#: ../luksformat:96 +msgid "The passphrases you entered were not identical\n" +msgstr "" + +#: ../luksformat:105 +#, perl-format +msgid "Could not format device with file system %s" +msgstr "" diff --git a/debian/scripts/suspend/cryptsetup-suspend-wrapper b/debian/scripts/suspend/cryptsetup-suspend-wrapper new file mode 100644 index 0000000..953196c --- /dev/null +++ b/debian/scripts/suspend/cryptsetup-suspend-wrapper @@ -0,0 +1,320 @@ +#!/bin/sh + +# Wrapper for cryptsetup-suspend(7) +# +# Copyright © 2019-2020 Tim <tim@systemli.org> +# © 2019-2020 Jonas Meurer <jonas@freesources.org> +# © 2020-2022 Guilhem Moulin <guilhem@debian.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/>. + +set -ue +PATH="/usr/sbin:/usr/bin:/sbin:/bin" +export PATH + +# import cryptsetup shell functions +[ -f /lib/cryptsetup/functions ] || return 0 +. /lib/cryptsetup/functions + +INITRAMFS_MNT="/run/cryptsetup/cryptsetup-suspend-initramfs" +SYSTEM_SLEEP_PATH="/lib/systemd/system-sleep" +CONFIG_FILE="/etc/cryptsetup/suspend.conf" +unset -v INITRAMFS_DIR + +read_config() { + # define defaults + export UNLOCK_SESSIONS="false" + export KEEP_INITRAMFS="false" + + # read config file if it exists + # shellcheck source=/etc/cryptsetup/suspend.conf + [ -f "$CONFIG_FILE" ] && . "$CONFIG_FILE" || true +} + +# run_dir ARGS... +# Run all executable scripts in directory SYSTEM_SLEEP_PATH with arguments ARGS +# mimic systemd behavior +run_dir() { + [ -d "$SYSTEM_SLEEP_PATH" ] || return 0 + find "$SYSTEM_SLEEP_PATH" -type f -executable -execdir {} "$@" \; +} + +log_error() { + # arg1 should be message + echo "Error: $1" | systemd-cat -t cryptsetup-suspend -p err + echo "Error: $1" >&2 +} + +mount_initramfs() { + local k v u IFS MemAvailable=0 SwapFree=0 new="n" + # update-initramfs(8) hardcodes /boot also: there is a `-b bootdir` + # option but no config file to put it to + local INITRAMFS="/boot/initrd.img-$(uname -r)" p + if [ ! -f "$INITRAMFS" ]; then + log_error "No initramfs found at $INITRAMFS" + exit 1 + fi + + if [ -d "$INITRAMFS_MNT" ] && [ ! "$INITRAMFS" -ot "$INITRAMFS_MNT" ]; then + # need to unpack again: initramfs is newer than what we unpacked earlier + if mountpoint -q "$INITRAMFS_MNT"; then + umount "$INITRAMFS_MNT" + fi + rmdir "$INITRAMFS_MNT" || exit 1 + fi + + if [ ! -d "$INITRAMFS_MNT" ]; then + # we need at about 300 MiB on ubuntu, 200 on debian + # https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773 + while IFS=" " read -r k v u; do + # /proc/meminfo format is documented in proc(5) + case "$u" in + MB) u=1048576;; + kB) u=1024;; + *) u=1;; + esac + case "$k" in + "MemAvailable:") MemAvailable=$((v*u));; + "SwapFree:") SwapFree=$((v*u));; + esac + done </proc/meminfo + if [ $((MemAvailable+SwapFree)) -lt $((300*1024*1024)) ]; then + log_error "Not enough memory available. Please close some programs or add swap space to suspend successfully." + exit 1 + fi + + mkdir -m0700 "$INITRAMFS_MNT" + mount -t ramfs -o nodev,mode=0700 ramfs "$INITRAMFS_MNT" + + # extract initrd.img to initramfs dir + unmkinitramfs "$INITRAMFS" "$INITRAMFS_MNT" + new="y" + fi + + # unmkinitramfs(8) extracts microcode into folders "early*" and the actual initramfs into "main" + if [ -f "$INITRAMFS_MNT/sbin/cryptsetup" ]; then + INITRAMFS_DIR="$INITRAMFS_MNT" + elif [ -f "$INITRAMFS_MNT/main/sbin/cryptsetup" ]; then + INITRAMFS_DIR="$INITRAMFS_MNT/main" + else + log_error "Directory $INITRAMFS_MNT has unpected content" >&2 + exit 1 + fi + + if [ "$new" = "y" ]; then + for p in /dev /proc /run /sys; do + if [ ! -d "$INITRAMFS_DIR$p" ]; then + mkdir -m0755 "$INITRAMFS_DIR$p" + fi + done + + # copy our binary to ramdisk + install -m0755 -t "$INITRAMFS_DIR/bin" /lib/cryptsetup/scripts/suspend/cryptsetup-suspend + + # copy all firmware files to ramdisk to prevent dead-lock + # see https://salsa.debian.org/mejo/cryptsetup-suspend/issues/38) + # TODO we should try to identify which firmwares need to be loaded + # and only copy those + if [ -d /lib/firmware ] && [ ! -d "$INITRAMFS_DIR/lib/firmware" ]; then + cp -dR -T -- /lib/firmware "$INITRAMFS_DIR/lib/firmware" + fi + fi + + # from initramfs-tools-core's /usr/share/initramfs-tools/init + mount -t devtmpfs -o noexec,nosuid,mode=0755 udev "$INITRAMFS_DIR/dev" + mount -t proc -o nodev,noexec,nosuid proc "$INITRAMFS_DIR/proc" + mount -t ramfs -o nodev,noexec,nosuid,mode=0755 ramfs "$INITRAMFS_DIR/run" + mount -t sysfs -o nodev,noexec,nosuid sysfs "$INITRAMFS_DIR/sys" + + [ -d "$INITRAMFS_DIR/dev/pts" ] || mkdir -m0755 "$INITRAMFS_DIR/dev/pts" + mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts "$INITRAMFS_DIR/dev/pts" || true + + # remount read-only, private and unbindable + mount -oremount,ro --make-rprivate --make-runbindable "$INITRAMFS_MNT" +} + +umount_initramfs() { + if [ -d "${INITRAMFS_DIR-}" ]; then + umount -- "$INITRAMFS_DIR/dev/pts" + umount -- "$INITRAMFS_DIR/dev" + umount -- "$INITRAMFS_DIR/proc" + umount -- "$INITRAMFS_DIR/run" + umount -- "$INITRAMFS_DIR/sys" + fi + if [ "$KEEP_INITRAMFS" != "true" ] || [ -z "${INITRAMFS_DIR+x}" ]; then + # always unmount if we error out before setting INITRAMFS_DIR + umount -- "$INITRAMFS_MNT" + rmdir -- "$INITRAMFS_MNT" + fi +} + +CGROUP_FREEZER= +freeze_cgroup() { + local c="$1" v + # freeze cgroup if non-frozen + if [ -f "$c" ] && v="$(cat <"$c")" && [ $v -eq 0 ]; then + echo 1 >"$c" + CGROUP_FREEZER="$c${CGROUP_FREEZER:+" $CGROUP_FREEZER"}" + fi +} +freeze_cgroups() { + local mycgroup c + + # freeze all machines/containers and user cgroups + freeze_cgroup "$hierarchy/machine.slice/cgroup.freeze" + freeze_cgroup "$hierarchy/user.slice/cgroup.freeze" + + # get my second level cgroup + mycgroup="$(grep -m1 "^0::" /proc/self/cgroup | cut -sd/ -f3)" + + # freeze all system cgroups except ours and systemd-suspend + for c in "$hierarchy"/system.slice/*/cgroup.freeze; do + if [ "$c" != "$hierarchy/system.slice/$mycgroup/cgroup.freeze" ] && \ + [ "${c#"$hierarchy/system.slice/systemd-suspend."}" = "$c" ]; then + freeze_cgroup "$c" + fi + done + + # freeze systemd itself + freeze_cgroup "$hierarchy/init.scope/cgroup.freeze" +} + +thaw_cgroups() { + local c + for c in $CGROUP_FREEZER; do + echo 0 >"$c" + done +} + +populate_ACTIVE_DEVICES() { + local DEV MAJ MIN + if ! dm_blkdevname "$CRYPTTAB_NAME" >/dev/null; then + # silently ignore unmapped devices + return 0 + elif [ "$(dmsetup info --noheadings -c -o subsystem -- "$CRYPTTAB_NAME")" != "CRYPT" ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: Subsystem mismatch" + return 1 + elif ! _resolve_device "$CRYPTTAB_SOURCE"; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: Missing source $CRYPTTAB_SOURCE" + return 1 + elif [ "$(dmsetup info -c --noheadings -o devnos_used -- "$CRYPTTAB_NAME" 2>/dev/null)" != "$MAJ:$MIN" ]; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: Source mismatch" + return 1 + fi + + if ! crypttab_parse_options --quiet; then + cryptsetup_message "ERROR: $CRYPTTAB_NAME: Unable to parse options field" + return 1 + elif [ "$CRYPTTAB_TYPE" != "luks" ]; then + # XXX does it even work with detached headers? + cryptsetup_message "WARNING: $CRYPTTAB_NAME: unable to suspend non-LUKS device" + return 0 + fi + + # XXX that's not robust since $CRYPTTAB_NAME might contain spaces or + # special characters; we need to create a NUL-delimited list in a + # file instead + ACTIVE_DEVICES="${ACTIVE_DEVICES:+"$ACTIVE_DEVICES "}$CRYPTTAB_NAME" +} + +clean_up() { + # we always want to run through the whole cleanup + set +e + + # thaw all frozen cgroups + thaw_cgroups + + # Run post-suspend scripts + run_dir post suspend + + umount_initramfs + + # unlock sessions + if [ "$UNLOCK_SESSIONS" = "true" ]; then + loginctl unlock-sessions + fi +} + +## Main script + +# check unified cgroups hierarchy +# https://github.com/systemd/systemd/blob/master/docs/CGROUP_DELEGATION.md +if [ -d /sys/fs/cgroup/system.slice ]; then + hierarchy="/sys/fs/cgroup" +elif [ -d /sys/fs/cgroup/unified/system.slice ]; then + # hybrid cgroup hierarchy + hierarchy="/sys/fs/cgroup/unified" +else + log_error "No unified cgroups hierarchy" + exit 1 +fi + +# check that not run as user +# XXX: We should catch also cases where libpam-systemd is not installed +if grep -Eq '^[0-9]+:[^:]*:/user\.slice/' /proc/self/cgroup; then + log_error "Don't run this script as user" + exit 1 +fi + +# always thaw cgroups, re-mount filesystems and remove initramfs at the end of the script +trap clean_up EXIT + +read_config + +# extract temporary filesystem to switch to +mount_initramfs + +# Run pre-suspend scripts +run_dir pre suspend + +# populate list of active crypt devices +ACTIVE_DEVICES="" +crypttab_foreach_entry populate_ACTIVE_DEVICES + +# freeze all cgroups but us +freeze_cgroups + +# No longer fail in case of errors +set +e + +# change into ramdisk +devices_remaining="$(chroot "$INITRAMFS_DIR" /bin/sh -c " + # suspend active luks devices (in reverse order) and system + /bin/cryptsetup-suspend --reverse $ACTIVE_DEVICES + + TABFILE=\"/cryptroot/crypttab\" + . /lib/cryptsetup/functions + + # resume active luks devices (only initramfs devices) + for dev in $ACTIVE_DEVICES; do + if crypttab_find_entry --quiet \"\$dev\"; then + DM_DISABLE_UDEV=y resume_device \"\$dev\" || sleep 5 + else + # write remaining devices to FD3 + printf \"%s \" \"\$dev\" >&3 + fi + done +" 3>&- 3>&1 >&2)" + +# resume remaining active luks devices (non-initramfs devices) +for dev in $devices_remaining; do + if crypttab_find_entry --quiet "$dev"; then + # explicitely disable udev support, cf. #1020553 + # XXX this is not ideal since udev might be required in some situations + # (detached header or key material on removable device comes to mind) + DM_DISABLE_UDEV=y resume_device "$dev" || true + else + log_error "'$dev' not found in /etc/crypttab" + fi +done diff --git a/debian/scripts/suspend/cryptsetup-suspend.c b/debian/scripts/suspend/cryptsetup-suspend.c new file mode 100644 index 0000000..af1b6f6 --- /dev/null +++ b/debian/scripts/suspend/cryptsetup-suspend.c @@ -0,0 +1,225 @@ +/* + * Small program to LUKS suspend devices before system suspend + * + * Copyright: (c) 2018 Guilhem Moulin <guilhem@debian.org> + * (c) 2018-2020 Jonas Meurer <jonas@freesources.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 3 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 <stdlib.h> +#include <stdio.h> +#include <stdbool.h> +#include <err.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/time.h> +#include <sys/resource.h> + +#include <libcryptsetup.h> + +#define SYSFS_POWER_SYNC_ON_SUSPEND "/sys/power/sync_on_suspend" +#define SYSFS_POWER_STATE "/sys/power/state" + +void usage() { + printf("Usage: cryptsetup-suspend [-r|--reverse] <blkdev> [<blkdev> ...]\n" + " -r, --reverse process luks devices in reverse order\n\n"); + exit(1); +} + +/* Calculate free memory (MemAvailable + SwapFree) from /proc/meminfo */ +uint32_t get_mem_swap_avail_kb() { + FILE *meminfo = fopen("/proc/meminfo", "r"); + if (meminfo == NULL) + err(EXIT_FAILURE, "couldn't open /proc/meminfo"); + + int mem_avail_kb, swap_free_kb = 0; + char line[256]; + while (fgets(line, sizeof(line), meminfo)) { + if (strncmp(line, "MemAvailable", strlen("MemAvailable")) == 0) { + if (sscanf(line, "MemAvailable: %d kB", &mem_avail_kb) != 1) + errx(EXIT_FAILURE, "couldn't read MemAvailable from /proc/meminfo"); + } else if (strncmp(line, "SwapFree", strlen("SwapFree")) == 0) { + if (sscanf(line, "SwapFree: %d kB", &swap_free_kb) != 1) + errx(EXIT_FAILURE, "couldn't read SwapFree from /proc/meminfo"); + } + } + fclose(meminfo); + + uint32_t mem_swap_avail_kb = mem_avail_kb + swap_free_kb; + if (mem_swap_avail_kb == 0) + errx(EXIT_FAILURE, "error reading available memory and swap from /proc/meminfo"); + + return mem_swap_avail_kb; +} + +int main(int argc, char *argv[]) { + int rv = 0; + bool reverse = 0; + int d_size; + bool sync_on_suspend_reset = 0; + FILE *sos = NULL; + + /* Process commandline arguments */ + if (argc < 2) { + usage(); + } else if ((strcmp(argv[1], "-r") == 0) || (strcmp(argv[1], "--reverse") == 0)) { + if (argc < 3) + usage(); + + reverse = 1; + d_size = argc-2; + } else { + d_size = argc-1; + } + + /* Read in devices */ + const char *devices[d_size]; + if (!reverse) { + for (int i = 0; i < d_size; i++) { + devices[i] = argv[i+1]; + } + } else { + for (int i = 0; i < d_size; i++) { + devices[i] = argv[argc-i-1]; + } + } + + /* Disable sync_on_suspend in Linux kernel + * + * Only available in Linux kernel >= 5.6 */ + if (access(SYSFS_POWER_SYNC_ON_SUSPEND, W_OK) < 0) { + if (errno == ENOENT) + warnx("kernel too old, can't disable sync on suspend"); + } else { + sos = fopen(SYSFS_POWER_SYNC_ON_SUSPEND, "r+"); + if (!sos) + err(EXIT_FAILURE, "couldn't open sysfs file"); + + int sos_c = fgetc(sos); + if (fgetc(sos) == EOF) + err(EXIT_FAILURE, "couldn't read from file"); + + if (sos_c == '0') { + /* Already disabled */ + } else if (sos_c == '1') { + sync_on_suspend_reset = 1; + if (fputc('0', sos) <= 0) + err(EXIT_FAILURE, "couldn't write to file"); + } else { + errx(EXIT_FAILURE, "unexpected value from %s", SYSFS_POWER_SYNC_ON_SUSPEND); + } + + fclose(sos); + } + + /* Change process priority to -20 (highest) to avoid races between + * the LUKS suspend(s) and the suspend-on-ram. */ + if (setpriority(PRIO_PROCESS, 0, -20) == -1) + warn("can't lower process priority to -20"); + + /* Get memory settings of keyslots from processed LUKS2 devices */ + uint32_t argon2i_max_memory_kb = 0; + for (int i = 0; i < d_size; i++) { + struct crypt_device *cd = NULL; + if (crypt_init_by_name(&cd, devices[i])) { + warnx("couldn't init LUKS device %s", devices[i]); + rv = EXIT_FAILURE; + } else { + /* Only LUKS2 devices may use argon2i PBKDF */ + if (strcmp(crypt_get_type(cd), CRYPT_LUKS2) != 0) + continue; + int ks_max = crypt_keyslot_max(crypt_get_type(cd)); + for (int j = 0; j < ks_max; j++) { + crypt_keyslot_info ki = crypt_keyslot_status(cd, j); + /* Only look at active keyslots */ + if (ki != CRYPT_SLOT_ACTIVE && ki != CRYPT_SLOT_ACTIVE_LAST) + continue; + struct crypt_pbkdf_type pbkdf_ki; + if (crypt_keyslot_get_pbkdf(cd, j, &pbkdf_ki) < 0) { + warn("couldn't get PBKDF for keyslot %d of device %s", j, devices[i]); + rv = EXIT_FAILURE; + } else { + if (pbkdf_ki.max_memory_kb > argon2i_max_memory_kb) + argon2i_max_memory_kb = pbkdf_ki.max_memory_kb; + } + } + } + crypt_free(cd); + } + + /* Add some more memory to be on the safe side + * TODO: find a reasonable value */ + argon2i_max_memory_kb += 2 * 1024; // 2MB + + /* Check if we have enough memory available to prevent mlock() from + * triggering the OOM killer. */ + uint32_t mem_swap_avail_kb = get_mem_swap_avail_kb(); + if (argon2i_max_memory_kb > mem_swap_avail_kb) { + errx(EXIT_FAILURE, "Error: Available memory (%d kb) less than required (%d kb)", + mem_swap_avail_kb, argon2i_max_memory_kb); + } + + /* Allocate and lock memory for later usage by LUKS resume in order to + * prevent swapping out after LUKS devices (which might include swap + * storage) have been suspended. */ + fprintf(stderr, "Allocating and mlocking memory: %d kb\n", argon2i_max_memory_kb); + char *mem; + if (!(mem = malloc(argon2i_max_memory_kb))) + err(EXIT_FAILURE, "couldn't allocate enough memory"); + if (mlock(mem, argon2i_max_memory_kb) == -1) + err(EXIT_FAILURE, "couldn't lock enough memory"); + /* Fill the allocated memory to make sure it's really reserved even if + * memory pages are copy-on-write. */ + size_t i; + size_t page_size = getpagesize(); + for (i = 0; i < argon2i_max_memory_kb; i += page_size) + mem[i] = 0; + + /* Do the final filesystem sync since we disabled sync_on_suspend in + * Linux kernel. */ + sync(); + + for (int i = 0; i < d_size; i++) { + struct crypt_device *cd = NULL; + if (crypt_init_by_name(&cd, devices[i]) || crypt_suspend(cd, devices[i])) { + warnx("couldn't suspend LUKS device %s", devices[i]); + rv = EXIT_FAILURE; + } + crypt_free(cd); + } + + fprintf(stderr, "Sleeping...\n"); + FILE *s = fopen(SYSFS_POWER_STATE, "w"); + if (!s) + err(EXIT_FAILURE, "failed to open %s", SYSFS_POWER_STATE); + if (fputs("mem", s) <= 0) + err(EXIT_FAILURE, "couldn't write to %s", SYSFS_POWER_STATE); + fclose(s); + fprintf(stderr, "Resuming...\n"); + + /* Restore original sync_on_suspend value */ + if (sync_on_suspend_reset) { + sos = fopen(SYSFS_POWER_SYNC_ON_SUSPEND, "w"); + if (!sos) + err(EXIT_FAILURE, "couldn't open sysfs file"); + if (fputc('1', sos) <= 0) + err(EXIT_FAILURE, "couldn't write to file"); + fclose(sos); + } + + return rv; +} diff --git a/debian/scripts/suspend/cryptsetup-suspend.shutdown b/debian/scripts/suspend/cryptsetup-suspend.shutdown new file mode 100644 index 0000000..f7d9f5d --- /dev/null +++ b/debian/scripts/suspend/cryptsetup-suspend.shutdown @@ -0,0 +1,3 @@ +#!/bin/sh +umount -R /run/cryptsetup/cryptsetup-suspend-initramfs +rmdir /run/cryptsetup/cryptsetup-suspend-initramfs diff --git a/debian/scripts/suspend/suspend.conf b/debian/scripts/suspend/suspend.conf new file mode 100644 index 0000000..79b2287 --- /dev/null +++ b/debian/scripts/suspend/suspend.conf @@ -0,0 +1,10 @@ +# Caution: This file will be sourced by another script. +# For security reasons, it should only be writable by root. + +# Automatically unlock user sessions after resume +# UNLOCK_SESSIONS="false" + +# Keep unpacked initramfs in RAM to accelerate suspension (this setting +# is ignored when the default initramfs image is newer than the +# cached/unpacked image) +# KEEP_INITRAMFS="false" diff --git a/debian/scripts/suspend/systemd/cryptsetup-suspend.conf b/debian/scripts/suspend/systemd/cryptsetup-suspend.conf new file mode 100644 index 0000000..10664cf --- /dev/null +++ b/debian/scripts/suspend/systemd/cryptsetup-suspend.conf @@ -0,0 +1,12 @@ +[Service] +# Protect against OOM killer. luksResume with Argon2 needs a lot of memory +OOMScoreAdjust=-1000 +# Give us higher priority +Nice=-10 +# override ExecStart of systemd-suspend.service +ExecStart= +# use VT 8 as workaround for https://gitlab.gnome.org/GNOME/gdm/issues/527 +# XXX on systems specifying the console= kernel parameter (such as a serial +# port) we should probably honor it +ExecStart=/bin/openvt -ws -c8 \ + /lib/cryptsetup/scripts/suspend/cryptsetup-suspend-wrapper |