diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:49:52 +0000 |
commit | 55944e5e40b1be2afc4855d8d2baf4b73d1876b5 (patch) | |
tree | 33f869f55a1b149e9b7c2b7e201867ca5dd52992 /src/veritysetup | |
parent | Initial commit. (diff) | |
download | systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.tar.xz systemd-55944e5e40b1be2afc4855d8d2baf4b73d1876b5.zip |
Adding upstream version 255.4.upstream/255.4
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/veritysetup')
-rw-r--r-- | src/veritysetup/meson.build | 15 | ||||
-rw-r--r-- | src/veritysetup/veritysetup-generator.c | 526 | ||||
-rw-r--r-- | src/veritysetup/veritysetup.c | 431 |
3 files changed, 972 insertions, 0 deletions
diff --git a/src/veritysetup/meson.build b/src/veritysetup/meson.build new file mode 100644 index 0000000..a7468df --- /dev/null +++ b/src/veritysetup/meson.build @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +executables += [ + libexec_template + { + 'name' : 'systemd-veritysetup', + 'conditions' : ['HAVE_LIBCRYPTSETUP'], + 'sources' : files('veritysetup.c'), + 'dependencies' : libcryptsetup, + }, + generator_template + { + 'name' : 'systemd-veritysetup-generator', + 'conditions' : ['HAVE_LIBCRYPTSETUP'], + 'sources' : files('veritysetup-generator.c'), + }, +] diff --git a/src/veritysetup/veritysetup-generator.c b/src/veritysetup/veritysetup-generator.c new file mode 100644 index 0000000..d55d4aa --- /dev/null +++ b/src/veritysetup/veritysetup-generator.c @@ -0,0 +1,526 @@ +/* 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 "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" + +#define SYSTEMD_VERITYSETUP_SERVICE_ROOT "systemd-veritysetup@root.service" +#define SYSTEMD_VERITYSETUP_SERVICE_USR "systemd-veritysetup@usr.service" + +static const char *arg_dest = NULL; +static bool arg_enabled = true; +static bool arg_read_veritytab = true; +static const char *arg_veritytab = NULL; +static char *arg_root_hash = NULL; +static char *arg_root_data_what = NULL; +static char *arg_root_hash_what = NULL; +static char *arg_root_options = NULL; +static char *arg_usr_hash = NULL; +static char *arg_usr_data_what = NULL; +static char *arg_usr_hash_what = NULL; +static char *arg_usr_options = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_data_what, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_what, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_options, freep); +STATIC_DESTRUCTOR_REGISTER(arg_usr_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_usr_data_what, freep); +STATIC_DESTRUCTOR_REGISTER(arg_usr_hash_what, freep); +STATIC_DESTRUCTOR_REGISTER(arg_usr_options, freep); + +static int create_special_device( + const char *name, + const char *service, + const char *roothash, + const char *data_what, + const char *hash_what, + const char *options) { + + _cleanup_free_ char *u = NULL, *v = NULL, *d = NULL, *e = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + /* Creates a systemd-veritysetup@.service instance for the special kernel cmdline specified root + usr devices. */ + + assert(name); + assert(service); + + /* If all three pieces of information are missing, then verity is turned off */ + if (!roothash && !data_what && !hash_what) + return 0; + + /* if one of them is missing however, the data is simply incomplete and this is an error */ + if (!roothash) + log_error("Verity information for %s incomplete, root hash unspecified.", name); + if (!data_what) + log_error("Verity information for %s incomplete, data device unspecified.", name); + if (!hash_what) + log_error("Verity information for %s incomplete, hash device unspecified.", name); + + if (!roothash || !data_what || !hash_what) + return -EINVAL; + + log_debug("Using %s verity data device %s, hash device %s, options %s, and hash %s.", name, data_what, hash_what, options, roothash); + + u = fstab_node_to_udev_node(data_what); + if (!u) + return log_oom(); + v = fstab_node_to_udev_node(hash_what); + if (!v) + return log_oom(); + + r = unit_name_from_path(u, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + r = unit_name_from_path(v, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + r = generator_open_unit_file(arg_dest, NULL, service, &f); + if (r < 0) + return r; + + r = generator_write_veritysetup_unit_section(f, "/proc/cmdline"); + if (r < 0) + return r; + + fprintf(f, + "Before=veritysetup.target\n" + "BindsTo=%s %s\n" + "After=%s %s\n", + d, e, + d, e); + + r = generator_write_veritysetup_service_section(f, name, u, v, roothash, options); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file unit %s: %m", service); + + r = generator_add_symlink(arg_dest, "veritysetup.target", "requires", service); + if (r < 0) + return r; + + return 0; +} + +static int create_root_device(void) { + return create_special_device("root", SYSTEMD_VERITYSETUP_SERVICE_ROOT, arg_root_hash, arg_root_data_what, arg_root_hash_what, arg_root_options); +} + +static int create_usr_device(void) { + return create_special_device("usr", SYSTEMD_VERITYSETUP_SERVICE_USR, arg_usr_hash, arg_usr_data_what, arg_usr_hash_what, arg_usr_options); +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + assert(key); + + if (streq(key, "systemd.verity")) { + + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning("Failed to parse verity= kernel command line switch %s. Ignoring.", value); + else + arg_enabled = r; + + } else if (streq(key, "veritytab")) { + + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning("Failed to parse veritytab= kernel command line switch %s. Ignoring.", value); + else + arg_read_veritytab = r; + + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_hash, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_root_data")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_data_what, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_root_hash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_hash_what, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_root_options")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_options, value); + if (r < 0) + return log_oom(); + + } else if (streq(key, "usrhash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_usr_hash, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_data")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_usr_data_what, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_hash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_usr_hash_what, value); + if (r < 0) + return log_oom(); + + } else if (proc_cmdline_key_streq(key, "systemd.verity_usr_options")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_usr_options, value); + if (r < 0) + return log_oom(); + + } + + return 0; +} + +static int determine_device( + const char *name, + const char *hash, + char **data_what, + char **hash_what) { + + sd_id128_t data_uuid, verity_uuid; + _cleanup_free_ void *m = NULL; + size_t l; + int r; + + assert(name); + assert(data_what); + assert(hash_what); + + if (!hash) + return 0; + + if (*data_what && *hash_what) + return 0; + + r = unhexmem(hash, strlen(hash), &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse hash: %s", hash); + if (l < sizeof(sd_id128_t)) { + log_debug("Root hash for %s is shorter than 128 bits (32 characters), ignoring for discovering verity partition.", name); + return 0; + } + + if (!*data_what) { + memcpy(&data_uuid, m, sizeof(data_uuid)); + + *data_what = path_join("/dev/disk/by-partuuid", SD_ID128_TO_UUID_STRING(data_uuid)); + if (!*data_what) + return log_oom(); + } + + if (!*hash_what) { + memcpy(&verity_uuid, (uint8_t*) m + l - sizeof(verity_uuid), sizeof(verity_uuid)); + + *hash_what = path_join("/dev/disk/by-partuuid", SD_ID128_TO_UUID_STRING(verity_uuid)); + if (!*hash_what) + return log_oom(); + } + + log_info("Using data device %s and hash device %s for %s.", *data_what, *hash_what, name); + + return 1; +} + +static int determine_devices(void) { + int r; + + r = determine_device("root", arg_root_hash, &arg_root_data_what, &arg_root_hash_what); + if (r < 0) + return r; + + return determine_device("usr", arg_usr_hash, &arg_usr_data_what, &arg_usr_hash_what); +} + +static bool attach_in_initrd(const char *name, const char *options) { + assert(name); + + /* Imply x-initrd.attach in case the volume name is among those defined in the Discoverable Partition + * Specification for partitions that we require to be mounted during the initrd → host transition, + * i.e. for the root fs itself, and /usr/. This mirrors similar behaviour in + * systemd-fstab-generator. */ + + return fstab_test_option(options, "x-initrd.attach\0") || + STR_IN_SET(name, "root", "usr"); +} + +static int create_veritytab_device( + const char *name, + const char *data_device, + const char *hash_device, + const char *roothash, + const char *options, + const char *source) { + + _cleanup_free_ char *n = NULL, *dd = NULL, *du = NULL, *hd = NULL, *hu = NULL, *e = NULL, + *du_escaped = NULL, *hu_escaped = NULL, *name_escaped = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *dmname; + bool noauto, nofail, netdev, need_loop = false; + int r; + + /* Creates a systemd-veritysetup@.service instance for volumes specified in /etc/veritytab. */ + + assert(name); + assert(data_device); + assert(hash_device); + assert(roothash); + + noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0"); + netdev = fstab_test_option(options, "_netdev\0"); + + name_escaped = specifier_escape(name); + if (!name_escaped) + return log_oom(); + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + du = fstab_node_to_udev_node(data_device); + if (!du) + return log_oom(); + + hu = fstab_node_to_udev_node(hash_device); + if (!hu) + return log_oom(); + + r = unit_name_build("systemd-veritysetup", e, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + du_escaped = specifier_escape(du); + if (!du_escaped) + return log_oom(); + + hu_escaped = specifier_escape(hu); + if (!hu_escaped) + return log_oom(); + + r = unit_name_from_path(du, ".device", &dd); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + r = unit_name_from_path(hu, ".device", &hd); + 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; + + r = generator_write_veritysetup_unit_section(f, source); + if (r < 0) + return r; + + if (netdev) + fprintf(f, "After=remote-fs-pre.target\n"); + + /* If initrd takes care of attaching the disk then it should also detach it during shutdown. */ + if (!attach_in_initrd(name, options)) + fprintf(f, + "Conflicts=umount.target\n" + "Before=umount.target\n"); + + if (!nofail) + fprintf(f, + "Before=%s\n", + netdev ? "remote-veritysetup.target" : "veritysetup.target"); + + if (path_startswith(du, "/dev/")) + fprintf(f, + "BindsTo=%s\n" + "After=%s\n", + dd, dd); + else { + fprintf(f, "RequiresMountsFor=%s\n", du_escaped); + need_loop = true; + } + + if (path_startswith(hu, "/dev/")) + fprintf(f, + "BindsTo=%s\n" + "After=%s\n", + hd, hd); + else { + fprintf(f, "RequiresMountsFor=%s\n", hu_escaped); + need_loop = true; + } + + if (need_loop) + /* For loopback devices make sure to explicitly load loop.ko, as this code might run very + * early where device nodes created via systemd-tmpfiles-setup-dev.service might not be + * around yet. Hence let's sync on the module itself. */ + fprintf(f, + "Wants=modprobe@loop.service\n" + "After=modprobe@loop.service\n"); + + r = generator_write_veritysetup_service_section(f, name, du, hu, roothash, options); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", n); + + if (!noauto) { + r = generator_add_symlink(arg_dest, + netdev ? "remote-veritysetup.target" : "veritysetup.target", + nofail ? "wants" : "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_veritytab_devices(void) { + _cleanup_fclose_ FILE *f = NULL; + unsigned veritytab_line = 0; + int r; + + if (!arg_read_veritytab) + return 0; + + r = fopen_unlocked(arg_veritytab, "re", &f); + if (r < 0) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open %s: %m", arg_veritytab); + return 0; + } + + for (;;) { + _cleanup_free_ char *line = NULL, *name = NULL, *data_device = NULL, *hash_device = NULL, + *roothash = NULL, *options = NULL; + char *data_uuid, *hash_uuid; + + r = read_stripped_line(f, LONG_LINE_MAX, &line); + if (r < 0) + return log_error_errno(r, "Failed to read %s: %m", arg_veritytab); + if (r == 0) + break; + + veritytab_line++; + + if (IN_SET(line[0], 0, '#')) + continue; + + r = sscanf(line, "%ms %ms %ms %ms %ms", &name, &data_device, &hash_device, &roothash, &options); + if (!IN_SET(r, 4, 5)) { + log_error("Failed to parse %s:%u, ignoring.", arg_veritytab, veritytab_line); + continue; + } + + data_uuid = startswith(data_device, "UUID="); + if (!data_uuid) + data_uuid = path_startswith(data_device, "/dev/disk/by-uuid/"); + + hash_uuid = startswith(hash_device, "UUID="); + if (!hash_uuid) + hash_uuid = path_startswith(hash_device, "/dev/disk/by-uuid/"); + + r = create_veritytab_device( + name, + data_device, + hash_device, + roothash, + options, + arg_veritytab); + if (r < 0) + return r; + } + + return 0; +} + +static int run(const char *dest, const char *dest_early, const char *dest_late) { + int r; + + assert_se(arg_dest = dest); + + arg_veritytab = getenv("SYSTEMD_VERITYTAB") ?: "/etc/veritytab"; + + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + return log_warning_errno(r, "Failed to parse kernel command line: %m"); + + if (!arg_enabled) + return 0; + + r = add_veritytab_devices(); + if (r < 0) + return r; + + r = determine_devices(); + if (r < 0) + return r; + + r = create_root_device(); + if (r < 0) + return r; + + return create_usr_device(); +} + +DEFINE_MAIN_GENERATOR_FUNCTION(run); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c new file mode 100644 index 0000000..d73c2d3 --- /dev/null +++ b/src/veritysetup/veritysetup.c @@ -0,0 +1,431 @@ +/* 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 "fstab-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "main-func.h" +#include "parse-util.h" +#include "path-util.h" +#include "pretty-print.h" +#include "process-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static char *arg_hash = NULL; +static bool arg_superblock = true; +static int arg_format = 1; +static uint64_t arg_data_block_size = 4096; +static uint64_t arg_hash_block_size = 4096; +static uint64_t arg_data_blocks = 0; +static uint64_t arg_hash_offset = 0; +static void *arg_salt = NULL; +static uint64_t arg_salt_size = 32; +static char *arg_uuid = NULL; +static uint32_t arg_activate_flags = CRYPT_ACTIVATE_READONLY; +static char *arg_fec_what = NULL; +static uint64_t arg_fec_offset = 0; +static uint64_t arg_fec_roots = 2; +static char *arg_root_hash_signature = NULL; + +STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); +STATIC_DESTRUCTOR_REGISTER(arg_salt, freep); +STATIC_DESTRUCTOR_REGISTER(arg_uuid, freep); +STATIC_DESTRUCTOR_REGISTER(arg_fec_what, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_signature, freep); + +static int help(void) { + _cleanup_free_ char *link = NULL; + int r; + + r = terminal_urlify_man("systemd-veritysetup@.service", "8", &link); + if (r < 0) + return log_oom(); + + printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [OPTIONS]\n" + "%s detach VOLUME\n\n" + "Attach or detach a verity protected block device.\n" + "\nSee the %s for details.\n", + program_invocation_short_name, + program_invocation_short_name, + link); + + return 0; +} + +static int save_roothashsig_option(const char *option, bool strict) { + int r; + + if (path_is_absolute(option) || startswith(option, "base64:")) { + if (!HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Activation of verity device with signature requested, but cryptsetup does not support crypt_activate_by_signed_key()."); + + r = free_and_strdup_warn(&arg_root_hash_signature, option); + if (r < 0) + return r; + + return true; + } + + if (!strict) + return false; + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "root-hash-signature= expects either full path to signature file or " + "base64 string encoding signature prefixed by base64:."); +} + +static int parse_block_size(const char *t, uint64_t *size) { + uint64_t u; + int r; + + r = parse_size(t, 1024, &u); + if (r < 0) + return r; + + if (u < 512 || u > (512 * 1024)) + return -ERANGE; + + if ((u % 512) != 0 || !ISPOWEROF2(u)) + return -EINVAL; + + *size = u; + + return 0; +} + +static int parse_options(const char *options) { + int r; + + /* backward compatibility with the obsolete ROOTHASHSIG positional argument */ + r = save_roothashsig_option(options, /* strict= */ false); + if (r < 0) + return r; + if (r > 0) { + log_warning("Usage of ROOTHASHSIG positional argument is deprecated. " + "Please use the option root-hash-signature=%s instead.", options); + return 0; + } + + 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; + + if (STR_IN_SET(word, "noauto", "auto", "nofail", "fail", "_netdev")) + continue; + + if (isempty(word)) + continue; + else if (streq(word, "ignore-corruption")) + arg_activate_flags |= CRYPT_ACTIVATE_IGNORE_CORRUPTION; + else if (streq(word, "restart-on-corruption")) + arg_activate_flags |= CRYPT_ACTIVATE_RESTART_ON_CORRUPTION; + else if (streq(word, "ignore-zero-blocks")) + arg_activate_flags |= CRYPT_ACTIVATE_IGNORE_ZERO_BLOCKS; +#ifdef CRYPT_ACTIVATE_CHECK_AT_MOST_ONCE + else if (streq(word, "check-at-most-once")) + arg_activate_flags |= CRYPT_ACTIVATE_CHECK_AT_MOST_ONCE; +#endif +#ifdef CRYPT_ACTIVATE_PANIC_ON_CORRUPTION + else if (streq(word, "panic-on-corruption")) + arg_activate_flags |= CRYPT_ACTIVATE_PANIC_ON_CORRUPTION; +#endif + else if ((val = startswith(word, "superblock="))) { + + r = parse_boolean(val); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean '%s': %m", word); + + arg_superblock = r; + } else if ((val = startswith(word, "format="))) { + + if (!STR_IN_SET(val, "0", "1")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "format= expects either 0 (original Chrome OS version) or " + "1 (modern version)."); + + arg_format = val[0] - '0'; + } else if ((val = startswith(word, "data-block-size="))) { + uint64_t sz; + + r = parse_block_size(val, &sz); + if (r < 0) + return log_error_errno(r, "Failed to parse size '%s': %m", word); + + arg_data_block_size = sz; + } else if ((val = startswith(word, "hash-block-size="))) { + uint64_t sz; + + r = parse_block_size(val, &sz); + if (r < 0) + return log_error_errno(r, "Failed to parse size '%s': %m", word); + + arg_hash_block_size = sz; + } else if ((val = startswith(word, "data-blocks="))) { + uint64_t u; + + r = safe_atou64(val, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse number '%s': %m", word); + + arg_data_blocks = u; + } else if ((val = startswith(word, "hash-offset="))) { + uint64_t off; + + r = parse_size(val, 1024, &off); + if (r < 0) + return log_error_errno(r, "Failed to parse offset '%s': %m", word); + if (off % 512 != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "hash-offset= expects a 512-byte aligned value."); + + arg_hash_offset = off; + } else if ((val = startswith(word, "salt="))) { + + if (!string_is_safe(val)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "salt= is not valid."); + + if (isempty(val)) { + arg_salt = mfree(arg_salt); + arg_salt_size = 32; + } else if (streq(val, "-")) { + arg_salt = mfree(arg_salt); + arg_salt_size = 0; + } else { + size_t l; + void *m; + + r = unhexmem(val, strlen(val), &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse salt '%s': %m", word); + + free_and_replace(arg_salt, m); + arg_salt_size = l; + } + } else if ((val = startswith(word, "uuid="))) { + + r = sd_id128_from_string(val, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse UUID '%s': %m", word); + + r = free_and_strdup(&arg_uuid, val); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "hash="))) { + + r = free_and_strdup(&arg_hash, val); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "fec-device="))) { + _cleanup_free_ char *what = NULL; + + what = fstab_node_to_udev_node(val); + if (!what) + return log_oom(); + + if (!path_is_absolute(what)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an absolute path."); + + if (!path_is_normalized(what)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-device= expects an normalized path."); + + r = free_and_strdup(&arg_fec_what, what); + if (r < 0) + return log_oom(); + } else if ((val = startswith(word, "fec-offset="))) { + uint64_t off; + + r = parse_size(val, 1024, &off); + if (r < 0) + return log_error_errno(r, "Failed to parse offset '%s': %m", word); + if (off % 512 != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-offset= expects a 512-byte aligned value."); + + arg_fec_offset = off; + } else if ((val = startswith(word, "fec-roots="))) { + uint64_t u; + + r = safe_atou64(val, &u); + if (r < 0) + return log_error_errno(r, "Failed to parse number '%s', ignoring: %m", word); + if (u < 2 || u > 24) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "fec-rootfs= expects a value between 2 and 24 (including)."); + + arg_fec_roots = u; + } else if ((val = startswith(word, "root-hash-signature="))) { + r = save_roothashsig_option(val, /* strict= */ true); + if (r < 0) + return r; + + } else + log_warning("Encountered unknown option '%s', ignoring.", word); + } + + return r; +} + +static int run(int argc, char *argv[]) { + _cleanup_(crypt_freep) struct crypt_device *cd = NULL; + const char *verb; + int r; + + if (argv_looks_like_help(argc, argv)) + return help(); + + if (argc < 3) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "This program requires at least two arguments."); + + log_setup(); + + cryptsetup_enable_logging(NULL); + + umask(0022); + + verb = argv[1]; + + if (streq(verb, "attach")) { + const char *volume, *data_device, *verity_device, *root_hash, *options; + _cleanup_free_ void *m = NULL; + struct crypt_params_verity p = {}; + crypt_status_info status; + size_t l; + + if (argc < 6) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "attach requires at least four arguments."); + + volume = argv[2]; + data_device = argv[3]; + verity_device = argv[4]; + root_hash = argv[5]; + options = mangle_none(argc > 6 ? argv[6] : NULL); + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = unhexmem(root_hash, SIZE_MAX, &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %m"); + + r = crypt_init(&cd, verity_device); + if (r < 0) + return log_error_errno(r, "Failed to open verity device %s: %m", verity_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 (options) { + r = parse_options(options); + if (r < 0) + return log_error_errno(r, "Failed to parse options: %m"); + } + + if (arg_superblock) { + p = (struct crypt_params_verity) { + .fec_device = arg_fec_what, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + }; + + r = crypt_load(cd, CRYPT_VERITY, &p); + if (r < 0) + return log_error_errno(r, "Failed to load verity superblock: %m"); + } else { + p = (struct crypt_params_verity) { + .hash_name = arg_hash, + .data_device = data_device, + .fec_device = arg_fec_what, + .salt = arg_salt, + .salt_size = arg_salt_size, + .hash_type = arg_format, + .data_block_size = arg_data_block_size, + .hash_block_size = arg_hash_block_size, + .data_size = arg_data_blocks, + .hash_area_offset = arg_hash_offset, + .fec_area_offset = arg_fec_offset, + .fec_roots = arg_fec_roots, + .flags = CRYPT_VERITY_NO_HEADER, + }; + + r = crypt_format(cd, CRYPT_VERITY, NULL, NULL, arg_uuid, NULL, 0, &p); + if (r < 0) + return log_error_errno(r, "Failed to format verity superblock: %m"); + } + + r = crypt_set_data_device(cd, data_device); + if (r < 0) + return log_error_errno(r, "Failed to configure data device: %m"); + + if (arg_root_hash_signature) { +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + _cleanup_free_ char *hash_sig = NULL; + size_t hash_sig_size; + char *value; + + if ((value = startswith(arg_root_hash_signature, "base64:"))) { + r = unbase64mem(value, strlen(value), (void *)&hash_sig, &hash_sig_size); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", arg_root_hash_signature); + } else { + r = read_full_file_full( + AT_FDCWD, arg_root_hash_signature, UINT64_MAX, SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, + NULL, + &hash_sig, &hash_sig_size); + if (r < 0) + return log_error_errno(r, "Failed to read root hash signature: %m"); + } + + r = crypt_activate_by_signed_key(cd, volume, m, l, hash_sig, hash_sig_size, arg_activate_flags); +#else + assert_not_reached(); +#endif + } else + r = crypt_activate_by_volume_key(cd, volume, m, l, arg_activate_flags); + if (r < 0) + return log_error_errno(r, "Failed to set up verity device '%s': %m", volume); + + } else if (streq(verb, "detach")) { + const char *volume; + + volume = argv[2]; + + if (!filename_is_valid(volume)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Volume name '%s' is not valid.", volume); + + r = crypt_init_by_name(&cd, volume); + if (r == -ENODEV) { + log_info("Volume %s 'already' inactive.", volume); + return 0; + } + if (r < 0) + return log_error_errno(r, "crypt_init_by_name() for volume '%s' failed: %m", volume); + + cryptsetup_enable_logging(cd); + + r = crypt_deactivate(cd, volume); + if (r < 0) + return log_error_errno(r, "Failed to deactivate volume '%s': %m", volume); + + } else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown verb %s.", verb); + + return 0; +} + +DEFINE_MAIN_FUNCTION(run); |