summaryrefslogtreecommitdiff
path: root/src/integritysetup
diff options
context:
space:
mode:
authorTony Asleson <tasleson@redhat.com>2021-09-26 11:53:42 -0500
committerTony Asleson <tasleson@redhat.com>2021-10-15 10:19:54 -0500
commit1f1a2243c0920bed1ba0ffd8e94e1de0172259ac (patch)
tree88a345aa7a9d6a92bfaf4765c4e36f7f8471d959 /src/integritysetup
parent9a2a6ec4e31abe4b58b140767a82200f79c8645f (diff)
downloadsystemd-1f1a2243c0920bed1ba0ffd8e94e1de0172259ac.tar.gz
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.
Diffstat (limited to 'src/integritysetup')
-rw-r--r--src/integritysetup/integrity-util.c66
-rw-r--r--src/integritysetup/integrity-util.h19
-rw-r--r--src/integritysetup/integritysetup-generator.c181
-rw-r--r--src/integritysetup/integritysetup.c197
4 files changed, 463 insertions, 0 deletions
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);