diff options
-rw-r--r-- | docs/ENVIRONMENT.md | 4 | ||||
-rw-r--r-- | man/integritytab.xml | 161 | ||||
-rw-r--r-- | man/rules/meson.build | 6 | ||||
-rw-r--r-- | man/systemd-integritysetup-generator.xml | 48 | ||||
-rw-r--r-- | man/systemd-integritysetup@.service.xml | 95 | ||||
-rw-r--r-- | man/systemd.special.xml | 2 | ||||
-rw-r--r-- | meson.build | 20 | ||||
-rw-r--r-- | src/integritysetup/integrity-util.c | 66 | ||||
-rw-r--r-- | src/integritysetup/integrity-util.h | 19 | ||||
-rw-r--r-- | src/integritysetup/integritysetup-generator.c | 181 | ||||
-rw-r--r-- | src/integritysetup/integritysetup.c | 197 | ||||
-rw-r--r-- | units/integritysetup-pre.target | 14 | ||||
-rw-r--r-- | units/integritysetup.target | 12 | ||||
-rw-r--r-- | units/meson.build | 3 |
14 files changed, 828 insertions, 0 deletions
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 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<!-- + SPDX-License-Identifier: LGPL-2.1-or-later + +--> +<refentry id="integritytab" conditional='HAVE_LIBCRYPTSETUP' xmlns:xi="http://www.w3.org/2001/XInclude"> + + <refentryinfo> + <title>integritytab</title> + <productname>systemd</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>integritytab</refentrytitle> + <manvolnum>5</manvolnum> + </refmeta> + + <refnamediv> + <refname>integritytab</refname> + <refpurpose>Configuration for integrity block devices</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/etc/integritytab</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para>The <filename>/etc/integritytab</filename> file describes + integrity protected block devices that are set up during + system boot.</para> + + <para>Empty lines and lines starting with the <literal>#</literal> + character are ignored. Each of the remaining lines describes one + verity integrity protected block device. Fields are delimited by + white space.</para> + + <para>Each line is in the form<programlisting><replaceable>volume-name</replaceable> <replaceable>block-device</replaceable> + <replaceable>[keyfile|-]</replaceable> <replaceable>[options|-]</replaceable></programlisting> + The first two fields are mandatory, the remaining two are optional and only required if user specified non-default options during integrity format.</para> + + <para>The first field contains the name of the resulting integrity volume; its block device is set up + below <filename>/dev/mapper/</filename>.</para> + + <para>The second field contains a path to the underlying block device, or a specification of a block device via + <literal>UUID=</literal> followed by the UUID, + <literal>PARTUUID=</literal> followed by the partition UUID, + <literal>LABEL=</literal> followed by the label, + <literal>PARTLABEL=</literal> followed by the partition label, + </para> + + <para>The third field if present contains an absolute filename path to a key file or a <literal>-</literal> + to specify none. When the filename is present, the "integrity-algorithm" defaults to <literal>hmac-sha256</literal> + 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. + </para> + + <para>The fourth field, if present, is a comma-delimited list of options or a <literal>-</literal> to specify none. The following options are + recognized:</para> + <variablelist> + + <varlistentry> + <term><option>allow-discards</option></term> + + <listitem><para> + Allow the use of discard (TRIM) requests for the device. + This option is available since the Linux kernel version 5.7. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>journal-watermark=[0..100]%</option></term> + + <listitem><para> + 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. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>journal-commit-time=[0..N]</option></term> + + <listitem><para> + 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. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>data-device=/dev/disk/by-...</option></term> + + <listitem><para> + 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. + </para></listitem> + </varlistentry> + + <varlistentry> + <term><option>integrity-algorithm=[crc32c|crc32|sha1|sha256|hmac-sha256]</option></term> + + <listitem><para> + The algorithm used for integrity checking. The default is crc32c. Must match option used during format. + </para></listitem> + </varlistentry> + </variablelist> + + <para>At early boot and when the system manager configuration is + reloaded, this file is translated into native systemd units by + <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>Examples</title> + <example> + <title>/etc/integritytab</title> + <para>Set up two integrity protected block devices. </para> + + <programlisting>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 +</programlisting> + </example> + + <example> + <title>/etc/integritytab</title> + <para>Set up 1 integrity protected block device using defaults </para> + + <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8</programlisting> + </example> + + <example> + <title>/etc/integritytab</title> + <para>Set up 1 integrity device using existing data block device which contains user data </para> + + <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 - data-device=/dev/disk/by-uuid/9276d9c0-d4e3-4297-b4ff-3307cd0d092f</programlisting> + </example> + + <example> + <title>/etc/integritytab</title> + <para>Set up 1 integrity device using a HMAC key file using defaults </para> + + <programlisting>home PARTUUID=4973d0b8-1b15-c449-96ec-94bab7f6a7b8 /etc/hmac.key</programlisting> + </example> + + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + </para> + </refsect1> + +</refentry> 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 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<!-- SPDX-License-Identifier: LGPL-2.1-or-later --> +<refentry id="systemd-integritysetup-generator" conditional='HAVE_LIBCRYPTSETUP'> + + <refentryinfo> + <title>systemd-integritysetup-generator</title> + <productname>systemd</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-integritysetup-generator</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-integritysetup-generator</refname> + <refpurpose>Unit generator for integrity protected block devices</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>/usr/lib/systemd/system-generators/systemd-integritysetup-generator</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><filename>systemd-integritysetup-generator</filename> is a generator that translates <filename>/etc/integritytab</filename> entries into + native systemd units early at boot. This will create + <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry> + units as necessary.</para> + + <para><command>systemd-integritysetup-generator</command> implements + <citerefentry><refentrytitle>systemd.generator</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-integritysetup@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + </para> + </refsect1> + +</refentry> 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 @@ +<?xml version="1.0"?> +<!--*-nxml-*--> +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" + "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> +<!-- SPDX-License-Identifier: LGPL-2.1-or-later --> +<refentry id="systemd-integritysetup@.service" conditional='HAVE_LIBCRYPTSETUP'> + + <refentryinfo> + <title>systemd-integritysetup@.service</title> + <productname>systemd</productname> + </refentryinfo> + + <refmeta> + <refentrytitle>systemd-integritysetup@.service</refentrytitle> + <manvolnum>8</manvolnum> + </refmeta> + + <refnamediv> + <refname>systemd-integritysetup@.service</refname> + <refname>systemd-integritysetup</refname> + <refpurpose>Disk integrity protection logic</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <para><filename>systemd-integritysetup@.service</filename></para> + <para><filename>/usr/lib/systemd/systemd-integritysetup</filename></para> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para><filename>systemd-integritysetup@.service</filename> is a service responsible for setting up integrity + protected block devices. It should be instantiated for each device that requires integrity + protection.</para> + + <para>At early boot and when the system manager configuration is reloaded, entries from /etc/integritytab are converted into + <filename>systemd-integritysetup@.service</filename> units by + <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para> + + <para><filename>systemd-integritysetup@.service</filename> calls <command>systemd-integritysetup</command>.</para> + </refsect1> + + <refsect1> + <title>Commands</title> + + <para>The following commands are understood by <command>systemd-integritysetup</command>:</para> + + <variablelist> + <varlistentry> + <term> + <option>attach</option> + <replaceable>volume</replaceable> + <replaceable>device</replaceable> + [<replaceable>key-file|-</replaceable>] + [<replaceable>option(s)|-</replaceable>] + </term> + + <listitem><para>Create a block device <replaceable>volume</replaceable> using + <replaceable>device</replaceable>. See integritytab man page and + <ulink url="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-integrity.html"> + Kernel dm-integrity</ulink> documentation for details. + </para></listitem> + </varlistentry> + + <varlistentry> + <term> + <option>detach</option> + <replaceable>volume</replaceable> + </term> + + <listitem><para>Detach (destroy) the block device + <replaceable>volume</replaceable>.</para></listitem> + </varlistentry> + + <varlistentry> + <term> + <option>help</option> + </term> + + <listitem><para>Print short information about command syntax.</para></listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1> + <title>See Also</title> + <para> + <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry><refentrytitle>integritytab</refentrytitle><manvolnum>5</manvolnum></citerefentry>, + <citerefentry><refentrytitle>systemd-integritysetup-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>integritysetup</refentrytitle><manvolnum>8</manvolnum></citerefentry> + </para> + </refsect1> + +</refentry> 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 @@ <filename>initrd-root-device.target</filename>, <filename>initrd-root-fs.target</filename>, <filename>initrd-usr-fs.target</filename>, + <filename>integritysetup-pre.target</filename>, + <filename>integritysetup.target</filename>, <filename>kbrequest.target</filename>, <filename>kexec.target</filename>, <filename>local-fs-pre.target</filename>, 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 <stdint.h> + +#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 <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <unistd.h> + +#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 <errno.h> +#include <stdio.h> +#include <sys/stat.h> + +#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', '', |