From 9a2a6ec4e31abe4b58b140767a82200f79c8645f Mon Sep 17 00:00:00 2001 From: Tony Asleson Date: Wed, 13 Oct 2021 14:18:12 -0500 Subject: [PATCH 1/2] dm-verity: Remove usage of integrity There is a difference between dm-verity and dm-integrity. Remove usage of integrity from verity documentation in man pages and target files. --- man/systemd-veritysetup-generator.xml | 8 ++++---- man/systemd-veritysetup@.service.xml | 8 ++++---- man/veritytab.xml | 14 +++++++------- src/veritysetup/veritysetup-generator.c | 2 +- src/veritysetup/veritysetup.c | 2 +- units/remote-veritysetup.target | 2 +- units/veritysetup-pre.target | 2 +- units/veritysetup.target | 2 +- 8 files changed, 20 insertions(+), 20 deletions(-) diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml index bf5e705f83..3c9ee6788f 100644 --- a/man/systemd-veritysetup-generator.xml +++ b/man/systemd-veritysetup-generator.xml @@ -17,7 +17,7 @@ systemd-veritysetup-generator - Unit generator for integrity protected block devices + Unit generator for verity protected block devices @@ -28,7 +28,7 @@ Description systemd-veritysetup-generator is a generator that translates kernel command line options - configuring integrity-protected block devices (verity) into native systemd units early at boot and when + configuring verity protected block devices into native systemd units early at boot and when configuration of the system manager is reloaded. This will create systemd-veritysetup@.service8 units as necessary. @@ -66,7 +66,7 @@ data devices to use are automatically derived from the specified hash value. Specifically, the data partition device is looked for under a GPT partition UUID derived from the first 128bit of the root hash, the hash partition device is looked for under a GPT partition UUID derived from the last 128bit of the root hash. Hence - it is usually sufficient to specify the root hash to boot from an integrity protected root file system, as + it is usually sufficient to specify the root hash to boot from a verity protected root file system, as device paths are automatically determined from it — as long as the partition table is properly set up. @@ -76,7 +76,7 @@ systemd.verity_root_hash= These two settings take block device paths as arguments and may be used to explicitly - configure the data partition and hash partition to use for setting up the integrity protection for the root file + configure the data partition and hash partition to use for setting up the verity protection for the root file system. If not specified, these paths are automatically derived from the roothash= argument (see above). diff --git a/man/systemd-veritysetup@.service.xml b/man/systemd-veritysetup@.service.xml index 70f08374e2..0f21c2fbbf 100644 --- a/man/systemd-veritysetup@.service.xml +++ b/man/systemd-veritysetup@.service.xml @@ -18,7 +18,7 @@ systemd-veritysetup@.service systemd-veritysetup - Disk integrity protection logic + Disk verity protection logic @@ -29,12 +29,12 @@ Description - systemd-veritysetup@.service is a service responsible for setting up integrity - protection (verity) block devices. It should be instantiated for each device that requires integrity + systemd-veritysetup@.service is a service responsible for setting up verity + protection block devices. It should be instantiated for each device that requires verity protection. At early boot and when the system manager configuration is reloaded kernel command line configuration for - integrity protected block devices is translated into systemd-veritysetup@.service units by + verity protected block devices is translated into systemd-veritysetup@.service units by systemd-veritysetup-generator8. systemd-veritysetup@.service calls systemd-veritysetup. diff --git a/man/veritytab.xml b/man/veritytab.xml index d29e9f5f2d..28dce1fe38 100644 --- a/man/veritytab.xml +++ b/man/veritytab.xml @@ -33,12 +33,12 @@ This is based on crypttab(5). Description The /etc/veritytab file describes - verity integrity protected block devices that are set up during + verity protected block devices that are set up during system boot. Empty lines and lines starting with the # character are ignored. Each of the remaining lines describes one - verity integrity protected block device. Fields are delimited by + verity protected block device. Fields are delimited by white space. Each line is in the formvolume-name data-device hash-device roothash options @@ -65,7 +65,7 @@ This is based on crypttab(5). - Defines what to do if data integrity problem is detected (data corruption). Without these + Defines what to do if a data verity problem is detected (data corruption). Without these options kernel fails the IO operation with I/O error. With --ignore-corruption option the corruption is only logged. With --restart-on-corruption or --panic-on-corruption the kernel is restarted (panicked) immediately. @@ -149,18 +149,18 @@ This is based on crypttab(5). - Setup this verity integrity protected block device in the initramfs, similarly to + Setup this verity protected block device in the initramfs, similarly to systemd.mount5 units marked with . Although it's not necessary to mark the mount entry for the root file system with , is still recommended with - the verity integrity protected block device containing the root file system as otherwise systemd + the verity protected block device containing the root file system as otherwise systemd will attempt to detach the device during the regular system shutdown while it's still in use. With this option the device will still be detached but later after the root file system is unmounted. - All other verity integrity protected block devices that contain file systems mounted in the + All other verity protected block devices that contain file systems mounted in the initramfs should use this option. @@ -176,7 +176,7 @@ This is based on crypttab(5). Examples /etc/veritytab example - Set up two verity integrity protected block devices. One using device blocks, another using files. + Set up two verity protected block devices. One using device blocks, another using files. usr PARTUUID=783e45ae-7aa3-484a-beef-a80ff9c19cbb PARTUUID=21dc1dfe-4c33-8b48-98a9-918a22eb3e37 36e3f740ad502e2c25e2a23d9c7c17bf0fdad2300b7580842d4b7ec1fb0fa263 auto data /etc/data /etc/hash a5ee4b42f70ae1f46a08a7c92c2e0a20672ad2f514792730f5d49d7606ab8fdf auto diff --git a/src/veritysetup/veritysetup-generator.c b/src/veritysetup/veritysetup-generator.c index 1c68d28364..13181b86ed 100644 --- a/src/veritysetup/veritysetup-generator.c +++ b/src/veritysetup/veritysetup-generator.c @@ -100,7 +100,7 @@ static int create_device(void) { fprintf(f, "[Unit]\n" - "Description=Integrity Protection Setup for %%I\n" + "Description=Verity Protection Setup for %%I\n" "Documentation=man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n" "SourcePath=/proc/cmdline\n" "DefaultDependencies=no\n" diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index e58bae45d2..61973bf37a 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -30,7 +30,7 @@ static int help(void) { printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n" "%s detach VOLUME\n\n" - "Attach or detach an integrity protected block device.\n" + "Attach or detach a verity protected block device.\n" "\nSee the %s for details.\n", program_invocation_short_name, program_invocation_short_name, diff --git a/units/remote-veritysetup.target b/units/remote-veritysetup.target index bd9f71acef..bad28c3f05 100644 --- a/units/remote-veritysetup.target +++ b/units/remote-veritysetup.target @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Remote Verity Integrity Protected Volumes +Description=Remote Verity Protected Volumes Documentation=man:systemd.special(7) After=remote-fs-pre.target veritysetup-pre.target DefaultDependencies=no diff --git a/units/veritysetup-pre.target b/units/veritysetup-pre.target index be065f335f..869575a983 100644 --- a/units/veritysetup-pre.target +++ b/units/veritysetup-pre.target @@ -8,7 +8,7 @@ # (at your option) any later version. [Unit] -Description=Local Verity Integrity Protected Volumes (Pre) +Description=Local Verity Protected Volumes (Pre) Documentation=man:systemd.special(7) RefuseManualStart=yes Before=veritysetup.target diff --git a/units/veritysetup.target b/units/veritysetup.target index 0ac3ad3bd0..c75b15375e 100644 --- a/units/veritysetup.target +++ b/units/veritysetup.target @@ -8,5 +8,5 @@ # (at your option) any later version. [Unit] -Description=Local Verity Integrity Protected Volumes +Description=Local Verity Protected Volumes Documentation=man:systemd.special(7) From 1f1a2243c0920bed1ba0ffd8e94e1de0172259ac Mon Sep 17 00:00:00 2001 From: Tony Asleson Date: Sun, 26 Sep 2021 11:53:42 -0500 Subject: [PATCH 2/2] Add stand-alone dm-integrity support This adds support for dm integrity targets and an associated /etc/integritytab file which is required as the dm integrity device super block doesn't include all of the required metadata to bring up the device correctly. See integritytab man page for details. --- docs/ENVIRONMENT.md | 4 + man/integritytab.xml | 161 ++++++++++++++ man/rules/meson.build | 6 + man/systemd-integritysetup-generator.xml | 48 +++++ man/systemd-integritysetup@.service.xml | 95 +++++++++ man/systemd.special.xml | 2 + meson.build | 20 ++ src/integritysetup/integrity-util.c | 66 ++++++ src/integritysetup/integrity-util.h | 19 ++ src/integritysetup/integritysetup-generator.c | 181 ++++++++++++++++ src/integritysetup/integritysetup.c | 197 ++++++++++++++++++ units/integritysetup-pre.target | 14 ++ units/integritysetup.target | 12 ++ units/meson.build | 3 + 14 files changed, 828 insertions(+) create mode 100644 man/integritytab.xml create mode 100644 man/systemd-integritysetup-generator.xml create mode 100644 man/systemd-integritysetup@.service.xml create mode 100644 src/integritysetup/integrity-util.c create mode 100644 src/integritysetup/integrity-util.h create mode 100644 src/integritysetup/integritysetup-generator.c create mode 100644 src/integritysetup/integritysetup.c create mode 100644 units/integritysetup-pre.target create mode 100644 units/integritysetup.target diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 175bb8a819..aba9ede259 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -50,6 +50,10 @@ All tools: useful for debugging. Currently only supported by `systemd-cryptsetup-generator`. +* `$SYSTEMD_INTEGRITYTAB` — if set, use this path instead of + `/etc/integritytab`. Only useful for debugging. Currently only supported by + `systemd-integritysetup-generator`. + * `$SYSTEMD_VERITYTAB` — if set, use this path instead of `/etc/veritytab`. Only useful for debugging. Currently only supported by `systemd-veritysetup-generator`. diff --git a/man/integritytab.xml b/man/integritytab.xml new file mode 100644 index 0000000000..c2ad2573a0 --- /dev/null +++ b/man/integritytab.xml @@ -0,0 +1,161 @@ + + + + + + + + integritytab + systemd + + + + integritytab + 5 + + + + integritytab + Configuration for integrity block devices + + + + /etc/integritytab + + + + Description + + The /etc/integritytab file describes + integrity protected block devices that are set up during + system boot. + + Empty lines and lines starting with the # + character are ignored. Each of the remaining lines describes one + verity integrity protected block device. Fields are delimited by + white space. + + Each line is in the formvolume-name block-device + [keyfile|-] [options|-] + The first two fields are mandatory, the remaining two are optional and only required if user specified non-default options during integrity format. + + The first field contains the name of the resulting integrity volume; its block device is set up + below /dev/mapper/. + + The second field contains a path to the underlying block device, or a specification of a block device via + UUID= followed by the UUID, + PARTUUID= followed by the partition UUID, + LABEL= followed by the label, + PARTLABEL= followed by the partition label, + + + The third field if present contains an absolute filename path to a key file or a - + to specify none. When the filename is present, the "integrity-algorithm" defaults to hmac-sha256 + with the key length derived from the number of bytes in the key file. At this time the only supported integrity algorithm + when using key file is hmac-sha256. The maximum size of the key file is 4096 bytes. + + + The fourth field, if present, is a comma-delimited list of options or a - to specify none. The following options are + recognized: + + + + + + + Allow the use of discard (TRIM) requests for the device. + This option is available since the Linux kernel version 5.7. + + + + + + + + Journal watermark in percent. When the journal percentage exceeds this watermark, the journal flush will be started. Setting a value of + "0%" uses default value. + + + + + + + + Commit time in milliseconds. When this time passes (and no explicit flush operation was issued), the journal is written. Setting a value of + zero uses default value. + + + + + + + + Specify a separate block device that contains existing data. The second field specified in the + integritytab for block device then will contain calculated integrity tags and journal for data-device, + but not the end user data. + + + + + + + + The algorithm used for integrity checking. The default is crc32c. Must match option used during format. + + + + + At early boot and when the system manager configuration is + reloaded, this file is translated into native systemd units by + systemd-integritysetup-generator8. + + + + Examples + + /etc/integritytab + Set up two integrity protected block devices. + + home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 - journal-commit-time=10,allow-discards,journal-watermark=55% +data PARTUUID=5d4b1808-be76-774d-88af-03c4c3a41761 - allow-discards + + + + + /etc/integritytab + Set up 1 integrity protected block device using defaults + + home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 + + + + /etc/integritytab + Set up 1 integrity device using existing data block device which contains user data + + home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 - data-device=/dev/disk/by-uuid/9276d9c0-d4e3-4297-b4ff-3307cd0d092f + + + + /etc/integritytab + Set up 1 integrity device using a HMAC key file using defaults + + home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 /etc/hmac.key + + + + + + See Also + + systemd1, + systemd-integritysetup@.service8, + systemd-integritysetup-generator8, + integritysetup8, + + + + diff --git a/man/rules/meson.build b/man/rules/meson.build index f9c69f1846..ad7cc41c98 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -24,6 +24,7 @@ manpages = [ ['hostname', '5', [], ''], ['hostnamectl', '1', [], 'ENABLE_HOSTNAMED'], ['hwdb', '7', [], 'ENABLE_HWDB'], + ['integritytab', '5', [], 'HAVE_LIBCRYPTSETUP'], ['journal-remote.conf', '5', ['journal-remote.conf.d'], 'HAVE_MICROHTTPD'], ['journal-upload.conf', '5', ['journal-upload.conf.d'], 'HAVE_MICROHTTPD'], ['journalctl', '1', [], ''], @@ -882,6 +883,11 @@ manpages = [ '8', ['systemd-initctl', 'systemd-initctl.socket'], 'HAVE_SYSV_COMPAT'], + ['systemd-integritysetup-generator', '8', [], 'HAVE_LIBCRYPTSETUP'], + ['systemd-integritysetup@.service', + '8', + ['systemd-integritysetup'], + 'HAVE_LIBCRYPTSETUP'], ['systemd-journal-gatewayd.service', '8', ['systemd-journal-gatewayd', 'systemd-journal-gatewayd.socket'], diff --git a/man/systemd-integritysetup-generator.xml b/man/systemd-integritysetup-generator.xml new file mode 100644 index 0000000000..23eab015f6 --- /dev/null +++ b/man/systemd-integritysetup-generator.xml @@ -0,0 +1,48 @@ + + + + + + + + systemd-integritysetup-generator + systemd + + + + systemd-integritysetup-generator + 8 + + + + systemd-integritysetup-generator + Unit generator for integrity protected block devices + + + + /usr/lib/systemd/system-generators/systemd-integritysetup-generator + + + + Description + + systemd-integritysetup-generator is a generator that translates /etc/integritytab entries into + native systemd units early at boot. This will create + systemd-integritysetup@.service8 + units as necessary. + + systemd-integritysetup-generator implements + systemd.generator7. + + + + See Also + + systemd1, + systemd-integritysetup@.service8, + integritysetup8, + + + + diff --git a/man/systemd-integritysetup@.service.xml b/man/systemd-integritysetup@.service.xml new file mode 100644 index 0000000000..24336c262d --- /dev/null +++ b/man/systemd-integritysetup@.service.xml @@ -0,0 +1,95 @@ + + + + + + + + systemd-integritysetup@.service + systemd + + + + systemd-integritysetup@.service + 8 + + + + systemd-integritysetup@.service + systemd-integritysetup + Disk integrity protection logic + + + + systemd-integritysetup@.service + /usr/lib/systemd/systemd-integritysetup + + + + Description + + systemd-integritysetup@.service is a service responsible for setting up integrity + protected block devices. It should be instantiated for each device that requires integrity + protection. + + At early boot and when the system manager configuration is reloaded, entries from /etc/integritytab are converted into + systemd-integritysetup@.service units by + systemd-integritysetup-generator8. + + systemd-integritysetup@.service calls systemd-integritysetup. + + + + Commands + + The following commands are understood by systemd-integritysetup: + + + + + + volume + device + [key-file|-] + [option(s)|-] + + + Create a block device volume using + device. See integritytab man page and + + Kernel dm-integrity documentation for details. + + + + + + + volume + + + Detach (destroy) the block device + volume. + + + + + + + + Print short information about command syntax. + + + + + + See Also + + systemd1, + integritytab5, + systemd-integritysetup-generator8, + integritysetup8 + + + + diff --git a/man/systemd.special.xml b/man/systemd.special.xml index 8755b523ae..9eb0b4a2c2 100644 --- a/man/systemd.special.xml +++ b/man/systemd.special.xml @@ -48,6 +48,8 @@ initrd-root-device.target, initrd-root-fs.target, initrd-usr-fs.target, + integritysetup-pre.target, + integritysetup.target, kbrequest.target, kexec.target, local-fs-pre.target, diff --git a/meson.build b/meson.build index 6c5d4bb34c..df53c6156d 100644 --- a/meson.build +++ b/meson.build @@ -252,6 +252,7 @@ conf.set_quoted('SYSTEMD_GROWFS_PATH', rootlibexecdir / ' conf.set_quoted('SYSTEMD_HOMEWORK_PATH', rootlibexecdir / 'systemd-homework') conf.set_quoted('SYSTEMD_IMPORT_FS_PATH', rootlibexecdir / 'systemd-import-fs') conf.set_quoted('SYSTEMD_IMPORT_PATH', rootlibexecdir / 'systemd-import') +conf.set_quoted('SYSTEMD_INTEGRITYSETUP_PATH', rootlibexecdir / 'systemd-integritysetup') conf.set_quoted('SYSTEMD_KBD_MODEL_MAP', pkgdatadir / 'kbd-model-map') conf.set_quoted('SYSTEMD_LANGUAGE_FALLBACK_MAP', pkgdatadir / 'language-fallback-map') conf.set_quoted('SYSTEMD_MAKEFS_PATH', rootlibexecdir / 'systemd-makefs') @@ -2535,6 +2536,25 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 libp11kit], install_rpath : rootlibexecdir, install : true) + + executable( + 'systemd-integritysetup', + ['src/integritysetup/integritysetup.c', 'src/integritysetup/integrity-util.c'], + include_directories : includes, + link_with : [libshared], + dependencies : [libcryptsetup], + install_rpath : rootlibexecdir, + install : true, + install_dir : rootlibexecdir) + + executable( + 'systemd-integritysetup-generator', + ['src/integritysetup/integritysetup-generator.c', 'src/integritysetup/integrity-util.c'], + include_directories : includes, + link_with : [libshared], + install_rpath : rootlibexecdir, + install : true, + install_dir : systemgeneratordir) endif if conf.get('HAVE_SYSV_COMPAT') == 1 diff --git a/src/integritysetup/integrity-util.c b/src/integritysetup/integrity-util.c new file mode 100644 index 0000000000..5970a136b8 --- /dev/null +++ b/src/integritysetup/integrity-util.c @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include "integrity-util.h" + +#include "extract-word.h" +#include "fileio.h" +#include "path-util.h" +#include "percent-util.h" + + +static int supported_integrity_algorithm(char *user_supplied) { + if (!STR_IN_SET(user_supplied, "crc32", "crc32c", "sha1", "sha256", "hmac-sha256")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported integrity algorithm (%s)", user_supplied); + return 0; +} + +int parse_integrity_options( + const char *options, + uint32_t *ret_activate_flags, + int *ret_percent, + usec_t *ret_commit_time, + char **ret_data_device, + char **ret_integrity_alg) { + int r; + + for (;;) { + _cleanup_free_ char *word = NULL; + char *val; + + r = extract_first_word(&options, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS); + if (r < 0) + return log_error_errno(r, "Failed to parse options: %m"); + if (r == 0) + break; + else if (streq(word, "allow-discards")) { + if (ret_activate_flags) + *ret_activate_flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + } else if ((val = startswith(word, "journal-watermark="))) { + r = parse_percent(val); + if (r < 0) + return log_error_errno(r, "Failed to parse journal-watermark value or value out of range (%s)", val); + if (ret_percent) + *ret_percent = r; + } else if ((val = startswith(word, "journal-commit-time="))) { + usec_t tmp_commit_time; + r = parse_sec(val, &tmp_commit_time); + if (r < 0) + return log_error_errno(r, "Failed to parse journal-commit-time value (%s)", val); + if (ret_commit_time) + *ret_commit_time = tmp_commit_time; + } else if ((val = startswith(word, "data-device="))) { + r = free_and_strdup(ret_data_device, val); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "integrity-algorithm="))) { + r = free_and_strdup(ret_integrity_alg, val); + if (r < 0) + return log_oom(); + r = supported_integrity_algorithm(*ret_integrity_alg); + if (r < 0) + return r; + } else + log_warning("Encountered unknown option '%s', ignoring.", word); + } + + return r; +} diff --git a/src/integritysetup/integrity-util.h b/src/integritysetup/integrity-util.h new file mode 100644 index 0000000000..b27975c7db --- /dev/null +++ b/src/integritysetup/integrity-util.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "cryptsetup-util.h" +#include "time-util.h" + + +int parse_integrity_options( + const char *options, + uint32_t *ret_activate_flags, + int *ret_percent, + usec_t *ret_commit_time, + char **ret_data_device, + char **ret_integrity_alg); + +#define DM_HMAC_256 "hmac(sha256)" +#define DM_MAX_KEY_SIZE 4096 /* Maximum size of key allowed for dm-integrity */ diff --git a/src/integritysetup/integritysetup-generator.c b/src/integritysetup/integritysetup-generator.c new file mode 100644 index 0000000000..15f508902d --- /dev/null +++ b/src/integritysetup/integritysetup-generator.c @@ -0,0 +1,181 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "integrity-util.h" +#include "main-func.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "specifier.h" +#include "string-util.h" +#include "unit-name.h" + +static const char *arg_dest = NULL; +static const char *arg_integritytab = NULL; +static char *arg_options = NULL; +STATIC_DESTRUCTOR_REGISTER(arg_options, freep); + +static int create_disk( + const char *name, + const char *device, + const char *key_file, + const char *options) { + + _cleanup_free_ char *n = NULL, *dd = NULL, *e = NULL, *name_escaped = NULL, *key_file_escaped = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + char *dmname = NULL; + + assert(name); + assert(device); + + name_escaped = specifier_escape(name); + if (!name_escaped) + return log_oom(); + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-integritysetup", e, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + r = unit_name_from_path(device, ".device", &dd); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + r = generator_open_unit_file(arg_dest, NULL, n, &f); + if (r < 0) + return r; + + if (key_file) { + if (!path_is_absolute(key_file)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute file path %s", key_file); + + key_file_escaped = specifier_escape(key_file); + if (!key_file_escaped) + return log_oom(); + } + + if (options) { + r = parse_integrity_options(options, NULL, NULL, NULL, NULL, NULL); + if (r < 0) + return r; + } + + fprintf(f, + "[Unit]\n" + "Description=Integrity Setup for %%I\n" + "Documentation=man:integritytab(5) man:systemd-integritysetup-generator(8) man:systemd-integritysetup@.service(8)\n" + "SourcePath=%s\n" + "DefaultDependencies=no\n" + "IgnoreOnIsolate=true\n" + "After=integritysetup-pre.target systemd-udevd-kernel.socket\n" + "Before=blockdev@dev-mapper-%%i.target\n" + "Wants=blockdev@dev-mapper-%%i.target\n" + "Conflicts=umount.target\n" + "Before=integritysetup.target\n" + "BindsTo=%s\n" + "After=%s\n" + "Before=umount.target\n", + arg_integritytab, + dd, dd); + + fprintf(f, + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "TimeoutSec=0\n" + "ExecStart=" ROOTLIBEXECDIR "/systemd-integritysetup attach '%s' '%s' '%s' '%s'\n" + "ExecStop=" ROOTLIBEXECDIR "/systemd-integritysetup detach '%s'\n", + name_escaped, device, empty_to_dash(key_file_escaped), empty_to_dash(options), + name_escaped); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", n); + + r = generator_add_symlink(arg_dest, "integritysetup.target", "requires", n); + if (r < 0) + return r; + + dmname = strjoina("dev-mapper-", e, ".device"); + return generator_add_symlink(arg_dest, dmname, "requires", n); +} + +static int add_integritytab_devices(void) { + _cleanup_fclose_ FILE *f = NULL; + unsigned integritytab_line = 0; + int r; + + r = fopen_unlocked(arg_integritytab, "re", &f); + if (r < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", arg_integritytab); + return 0; + } + + for (;;) { + _cleanup_free_ char *line = NULL, *name = NULL, *device_id = NULL, *device_path = NULL, *key_file = NULL, *options = NULL; + char *l; + + r = read_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", arg_integritytab); + if (r == 0) + break; + + integritytab_line++; + + l = strstrip(line); + if (!l) + continue; + + if (IN_SET(l[0], 0, '#')) + continue; + + /* The key file and the options are optional */ + r = sscanf(l, "%ms %ms %ms %ms", &name, &device_id, &key_file, &options); + if (!IN_SET(r, 2, 3, 4)) { + log_error("Failed to parse %s:%u, ignoring.", l, integritytab_line); + continue; + } + + device_path = fstab_node_to_udev_node(device_id); + if (!device_path) { + log_error("Failed to find device %s:%u, ignoring.", device_id, integritytab_line); + continue; + } + + r = create_disk(name, device_path, empty_or_dash_to_null(key_file), empty_or_dash_to_null(options)); + if (r < 0) + return r; + } + + return 0; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + assert_se(arg_dest = dest); + + arg_integritytab = getenv("SYSTEMD_INTEGRITYTAB") ?: "/etc/integritytab"; + + return add_integritytab_devices(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/integritysetup/integritysetup.c b/src/integritysetup/integritysetup.c new file mode 100644 index 0000000000..d8cfd12e95 --- /dev/null +++ b/src/integritysetup/integritysetup.c @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include +#include +#include + +#include "alloc-util.h" +#include "cryptsetup-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "integrity-util.h" +#include "log.h" +#include "main-func.h" +#include "memory-util.h" +#include "path-util.h" +#include "parse-util.h" +#include "pretty-print.h" +#include "string-util.h" +#include "terminal-util.h" + +static uint32_t arg_activate_flags; +static int arg_percent; +static usec_t arg_commit_time; +static char *arg_existing_data_device; +static char *arg_integrity_algorithm; + +STATIC_DESTRUCTOR_REGISTER(arg_existing_data_device, freep); +STATIC_DESTRUCTOR_REGISTER(arg_integrity_algorithm, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-integritysetup@.service", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s attach VOLUME DEVICE [HMAC_KEY_FILE|-] [OPTIONS]\n" + "%s detach VOLUME\n\n" + "Attach or detach an integrity protected block device.\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + program_invocation_short_name, + link); + + return 0; +} + +static int load_key_file( + const char *key_file, + void **ret_key_file_contents, + size_t *ret_key_file_size) { + int r; + _cleanup_(erase_and_freep) char *tmp_key_file_contents = NULL; + size_t tmp_key_file_size; + + if (!path_is_absolute(key_file)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "key file not absolute path: %s", key_file); + + r = read_full_file_full( + AT_FDCWD, key_file, UINT64_MAX, DM_MAX_KEY_SIZE, + READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE|READ_FULL_FILE_CONNECT_SOCKET|READ_FULL_FILE_FAIL_WHEN_LARGER, + NULL, + &tmp_key_file_contents, &tmp_key_file_size); + if (r < 0) + return log_error_errno(r, "Failed to process key file: %m"); + + if (ret_key_file_contents && ret_key_file_size) { + *ret_key_file_contents = TAKE_PTR(tmp_key_file_contents); + *ret_key_file_size = tmp_key_file_size; + } + + return 0; +} + +static const char *integrity_algorithm_select(const void *key_file_buf) { + /* To keep a bit of sanity for end users, the subset of integrity + algorithms we support will match what is used in integritysetup */ + if (arg_integrity_algorithm) { + if (streq("hmac-sha256", arg_integrity_algorithm)) + return DM_HMAC_256; + return arg_integrity_algorithm; + } else if (key_file_buf) + return DM_HMAC_256; + return "crc32c"; +} + +static int run(int argc, char *argv[]) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + int r; + char *action, *volume; + + if (argc <= 1 || + strv_contains(strv_skip(argv, 1), "--help") || + strv_contains(strv_skip(argv, 1), "-h") || + streq(argv[1], "help")) + return help(); + + if (argc < 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); + + action = argv[1]; + volume = argv[2]; + + log_setup(); + + cryptsetup_enable_logging(NULL); + + umask(0022); + + if (streq(action, "attach")) { + /* attach name device optional_key_file optional_options */ + + crypt_status_info status; + _cleanup_(erase_and_freep) void *key_buf = NULL; + const char *device, *key_file, *options; + size_t key_buf_size = 0; + + if (argc < 4) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least three arguments."); + + if (argc > 6) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach has a maximum of five arguments."); + + device = argv[3]; + key_file = (argc > 4) ? empty_or_dash_to_null(argv[4]) : NULL; + options = (argc > 5) ? empty_or_dash_to_null(argv[5]) : NULL; + + if (key_file) { + r = load_key_file(key_file, &key_buf, &key_buf_size); + if (r < 0) + return r; + } + + if (options) { + r = parse_integrity_options(options, &arg_activate_flags, &arg_percent, + &arg_commit_time, &arg_existing_data_device, &arg_integrity_algorithm); + if (r < 0) + return r; + } + + r = crypt_init(&cd, device); + if (r < 0) + return log_error_errno(r, "Failed to open integrity device %s: %m", device); + + cryptsetup_enable_logging(cd); + + status = crypt_status(cd, volume); + if (IN_SET(status, CRYPT_ACTIVE, CRYPT_BUSY)) { + log_info("Volume %s already active.", volume); + return 0; + } + + if (!isempty(arg_existing_data_device)) { + r = crypt_init_data_device(&cd, device, arg_existing_data_device); + if (r < 0) + return log_error_errno(r, "Failed to add separate data device: %m"); + } + + r = crypt_load(cd, + CRYPT_INTEGRITY, + &(struct crypt_params_integrity) { + .journal_watermark = arg_percent, + .journal_commit_time = DIV_ROUND_UP(arg_commit_time, USEC_PER_SEC), + .integrity = integrity_algorithm_select(key_buf), + }); + if (r < 0) + return log_error_errno(r, "Failed to load integrity superblock: %m"); + + r = crypt_activate_by_volume_key(cd, volume, key_buf, key_buf_size, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up integrity device: %m"); + + } else if (streq(action, "detach")) { + + if (argc > 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "detach has a maximum of two arguments."); + + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) + return 0; + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() failed: %m"); + + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate: %m"); + + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", action); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); diff --git a/units/integritysetup-pre.target b/units/integritysetup-pre.target new file mode 100644 index 0000000000..da2aca9a28 --- /dev/null +++ b/units/integritysetup-pre.target @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Local Integrity Protected Volumes (Pre) +Documentation=man:systemd.special(7) +RefuseManualStart=yes +Before=integritysetup.target diff --git a/units/integritysetup.target b/units/integritysetup.target new file mode 100644 index 0000000000..371490f883 --- /dev/null +++ b/units/integritysetup.target @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Local Integrity Protected Volumes +Documentation=man:systemd.special(7) diff --git a/units/meson.build b/units/meson.build index 93c3bf608a..c106284e0f 100644 --- a/units/meson.build +++ b/units/meson.build @@ -13,6 +13,9 @@ units = [ ['veritysetup-pre.target', 'HAVE_LIBCRYPTSETUP'], ['veritysetup.target', 'HAVE_LIBCRYPTSETUP', 'sysinit.target.wants/'], + ['integritysetup-pre.target', 'HAVE_LIBCRYPTSETUP'], + ['integritysetup.target', 'HAVE_LIBCRYPTSETUP', + 'sysinit.target.wants/'], ['dev-hugepages.mount', '', 'sysinit.target.wants/'], ['dev-mqueue.mount', '',