diff --git a/README b/README index 4f4a21eeca..4269f0c73d 100644 --- a/README +++ b/README @@ -35,6 +35,7 @@ LICENSE: REQUIREMENTS: Linux kernel >= 3.13 Linux kernel >= 4.2 for unified cgroup hierarchy support + Linux kernel >= 5.4 for signed Verity images support Kernel Config Options: CONFIG_DEVTMPFS @@ -102,6 +103,9 @@ REQUIREMENTS: CONFIG_EFIVAR_FS CONFIG_EFI_PARTITION + Required for signed Verity images support: + CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG + We recommend to turn off Real-Time group scheduling in the kernel when using systemd. RT group scheduling effectively makes RT scheduling unavailable for most userspace, since it @@ -144,7 +148,7 @@ REQUIREMENTS: libblkid >= 2.24 (from util-linux) (optional) libkmod >= 15 (optional) PAM >= 1.1.2 (optional) - libcryptsetup (optional) + libcryptsetup (optional), >= 2.3.0 required for signed Verity images support libaudit (optional) libacl (optional) libselinux (optional) diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index abcddbf00a..e28572db6d 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -304,7 +304,7 @@ Single file system images (i.e. file systems without a surrounding partition table) can be opened using dm-verity if the integrity data is passed using the and - options. + (and optionally ) options. Any other partitions, such as foreign partitions or swap partitions are not mounted. May not be specified together with , . @@ -399,6 +399,18 @@ is read from it and automatically used, also as formatted hexadecimal characters. + + + + Takes a PKCS7 formatted binary signature of the option as a path + to a DER encoded signature file or as an ASCII base64 string encoding of the DER encoded signature, prefixed + by base64:. The dm-verity volume will only be opened if the signature of the root hash hex + string is valid and done by a public key present in the kernel keyring. If this option is not specified, but a + file with the .roothash.p7s suffix is found next to the image file, bearing otherwise the + same name (except if the image has the .raw suffix, in which case the signature file must + not have it in its name), the signature is read from it and automatically used. + + diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index de0cfac2a9..c828109d01 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -164,6 +164,20 @@ + + RootHashSignature= + + Takes a PKCS7 formatted binary signature of the RootHash= option as a path + to a DER encoded signature file or as an ASCII base64 string encoding of the DER encoded signature, prefixed + by base64:. The dm-verity volume will only be opened if the signature of the root hash + signature is valid and created by a public key present in the kernel keyring. If this option is not specified, + but a file with the .roothash.p7s suffix is found next to the image file, bearing otherwise + the same name (except if the image has the .raw suffix, in which case the signature file + must not have it in its name), the signature is read from it and automatically used. + + + + RootVerity= diff --git a/meson.build b/meson.build index 8f1d1b5897..827fdfc8cf 100644 --- a/meson.build +++ b/meson.build @@ -1035,6 +1035,8 @@ if want_libcryptsetup != 'false' and not skip_deps conf.set10('HAVE_CRYPT_SET_METADATA_SIZE', have and cc.has_function('crypt_set_metadata_size', dependencies : libcryptsetup)) + conf.set10('HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY', + have and cc.has_function('crypt_activate_by_signed_key', dependencies : libcryptsetup)) else have = false libcryptsetup = [] diff --git a/shell-completion/bash/systemd-nspawn b/shell-completion/bash/systemd-nspawn index a731167680..a8bd406fb3 100644 --- a/shell-completion/bash/systemd-nspawn +++ b/shell-completion/bash/systemd-nspawn @@ -71,7 +71,7 @@ _systemd_nspawn() { --pivot-root --property --private-users --network-namespace-path --network-ipvlan --network-veth-extra --network-zone -p --port --system-call-filter --overlay --overlay-ro --settings --rlimit --hostname --no-new-privileges --oom-score-adjust --cpu-affinity - --resolv-conf --timezone' + --resolv-conf --timezone --root-hash-sig' ) _init_completion || return @@ -183,6 +183,10 @@ _systemd_nspawn() { --timezone) comps=$( systemd-nspawn --timezone=help 2>/dev/null ) ;; + --root-hash-sig) + compopt -o nospace + comps=$( compgen -A file -- "$cur" ) + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 45b5c0c13a..41d64e8004 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -765,6 +765,25 @@ static int property_get_root_hash( return sd_bus_message_append_array(reply, 'y', c->root_hash, c->root_hash_size); } +static int property_get_root_hash_sig( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + return sd_bus_message_append_array(reply, 'y', c->root_hash_sig, c->root_hash_sig_size); +} + const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), @@ -809,6 +828,8 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("RootImage", "s", NULL, offsetof(ExecContext, root_image), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootHash", "ay", property_get_root_hash, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootHashPath", "s", NULL, offsetof(ExecContext, root_hash_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootHashSignature", "ay", property_get_root_hash_sig, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootHashSignaturePath", "s", NULL, offsetof(ExecContext, root_hash_sig_path), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootVerity", "s", NULL, offsetof(ExecContext, root_verity), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("CoredumpFilter", "t", property_get_coredump_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -1326,6 +1347,53 @@ int bus_exec_context_set_transient_property( return bus_set_transient_path(u, "RootHash", &c->root_hash_path, message, flags, error); } + if (streq(name, "RootHashSignature")) { + const void *roothash_sig_decoded; + size_t roothash_sig_decoded_size; + + r = sd_bus_message_read_array(message, 'y', &roothash_sig_decoded, &roothash_sig_decoded_size); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *encoded = NULL; + + if (roothash_sig_decoded_size == 0) { + c->root_hash_sig_path = mfree(c->root_hash_sig_path); + c->root_hash_sig = mfree(c->root_hash_sig); + c->root_hash_sig_size = 0; + + unit_write_settingf(u, flags, name, "RootHashSignature="); + } else { + _cleanup_free_ void *p; + ssize_t len; + + len = base64mem(roothash_sig_decoded, roothash_sig_decoded_size, &encoded); + if (len < 0) + return -ENOMEM; + + p = memdup(roothash_sig_decoded, roothash_sig_decoded_size); + if (!p) + return -ENOMEM; + + free_and_replace(c->root_hash_sig, p); + c->root_hash_sig_size = roothash_sig_decoded_size; + c->root_hash_sig_path = mfree(c->root_hash_sig_path); + + unit_write_settingf(u, flags, name, "RootHashSignature=base64:%s", encoded); + } + } + + return 1; + } + + if (streq(name, "RootHashSignaturePath")) { + c->root_hash_sig_size = 0; + c->root_hash_sig = mfree(c->root_hash_sig); + + return bus_set_transient_path(u, "RootHashSignature", &c->root_hash_sig_path, message, flags, error); + } + if (streq(name, "RootVerity")) return bus_set_transient_path(u, name, &c->root_verity, message, flags, error); diff --git a/src/core/execute.c b/src/core/execute.c index 0765f112fd..4bee1b1966 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2667,7 +2667,9 @@ static int apply_mount_namespace( needs_sandboxing ? context->protect_home : PROTECT_HOME_NO, needs_sandboxing ? context->protect_system : PROTECT_SYSTEM_NO, context->mount_flags, - context->root_hash, context->root_hash_size, context->root_hash_path, context->root_verity, + context->root_hash, context->root_hash_size, context->root_hash_path, + context->root_hash_sig, context->root_hash_sig_size, context->root_hash_sig_path, + context->root_verity, DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK|DISSECT_IMAGE_FSCK, error_path); @@ -4200,6 +4202,9 @@ void exec_context_done(ExecContext *c) { c->root_hash = mfree(c->root_hash); c->root_hash_size = 0; c->root_hash_path = mfree(c->root_hash_path); + c->root_hash_sig = mfree(c->root_hash_sig); + c->root_hash_sig_size = 0; + c->root_hash_sig_path = mfree(c->root_hash_sig_path); c->root_verity = mfree(c->root_verity); c->tty_path = mfree(c->tty_path); c->syslog_identifier = mfree(c->syslog_identifier); @@ -4615,6 +4620,17 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { if (c->root_hash_path) fprintf(f, "%sRootHash: %s\n", prefix, c->root_hash_path); + if (c->root_hash_sig) { + _cleanup_free_ char *encoded = NULL; + ssize_t len; + len = base64mem(c->root_hash_sig, c->root_hash_sig_size, &encoded); + if (len) + fprintf(f, "%sRootHashSignature: base64:%s\n", prefix, encoded); + } + + if (c->root_hash_sig_path) + fprintf(f, "%sRootHashSignature: %s\n", prefix, c->root_hash_sig_path); + if (c->root_verity) fprintf(f, "%sRootVerity: %s\n", prefix, c->root_verity); diff --git a/src/core/execute.h b/src/core/execute.h index 805709f2aa..6bd71c1740 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -155,9 +155,9 @@ struct ExecContext { char **unset_environment; struct rlimit *rlimit[_RLIMIT_MAX]; - char *working_directory, *root_directory, *root_image, *root_verity, *root_hash_path; - void *root_hash; - size_t root_hash_size; + char *working_directory, *root_directory, *root_image, *root_verity, *root_hash_path, *root_hash_sig_path; + void *root_hash, *root_hash_sig; + size_t root_hash_size, root_hash_sig_size; bool working_directory_missing_ok:1; bool working_directory_home:1; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 9cf959edd5..12ae78eb7d 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -24,6 +24,7 @@ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', $1.RootDirectory, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_directory) $1.RootImage, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_image) $1.RootHash, config_parse_exec_root_hash, 0, offsetof($1, exec_context) +$1.RootHashSignature, config_parse_exec_root_hash_sig, 0, offsetof($1, exec_context) $1.RootVerity, config_parse_unit_path_printf, true, offsetof($1, exec_context.root_verity) $1.User, config_parse_user_group_compat, 0, offsetof($1, exec_context.user) $1.Group, config_parse_user_group_compat, 0, offsetof($1, exec_context.group) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 09065ccb36..0445a3a2c9 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1472,6 +1472,66 @@ int config_parse_exec_root_hash( return 0; } +int config_parse_exec_root_hash_sig( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ void *roothash_sig_decoded = NULL; + char *value; + ExecContext *c = data; + size_t roothash_sig_decoded_size = 0; + int r; + + assert(data); + assert(filename); + assert(line); + assert(rvalue); + + if (isempty(rvalue)) { + /* Reset if the empty string is assigned */ + c->root_hash_sig_path = mfree(c->root_hash_sig_path); + c->root_hash_sig = mfree(c->root_hash_sig); + c->root_hash_sig_size = 0; + return 0; + } + + if (path_is_absolute(rvalue)) { + /* We have the path to a roothash signature to load and decode, eg: RootHashSignature=/foo/bar.roothash.p7s */ + _cleanup_free_ char *p = NULL; + + p = strdup(rvalue); + if (!p) + return -ENOMEM; + + free_and_replace(c->root_hash_sig_path, p); + c->root_hash_sig = mfree(c->root_hash_sig); + c->root_hash_sig_size = 0; + return 0; + } + + if (!(value = startswith(rvalue, "base64:"))) + return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature=, not a path but doesn't start with 'base64:', ignoring: %s", rvalue); + + /* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */ + r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode RootHashSignature=, ignoring: %s", rvalue); + + free_and_replace(c->root_hash_sig, roothash_sig_decoded); + c->root_hash_sig_size = roothash_sig_decoded_size; + c->root_hash_sig_path = mfree(c->root_hash_sig_path); + + return 0; +} + int config_parse_exec_cpu_affinity(const char *unit, const char *filename, unsigned line, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index f0e109da3a..ac3940a1b7 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -45,6 +45,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_sched_prio); CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_affinity); CONFIG_PARSER_PROTOTYPE(config_parse_exec_secure_bits); CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash); +CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash_sig); CONFIG_PARSER_PROTOTYPE(config_parse_capability_set); CONFIG_PARSER_PROTOTYPE(config_parse_exec_mount_flags); CONFIG_PARSER_PROTOTYPE(config_parse_timer); diff --git a/src/core/namespace.c b/src/core/namespace.c index 7bb4440747..785a469478 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1260,6 +1260,9 @@ int setup_namespace( const void *root_hash, size_t root_hash_size, const char *root_hash_path, + const void *root_hash_sig, + size_t root_hash_sig_size, + const char *root_hash_sig_path, const char *root_verity, DissectImageFlags dissect_image_flags, char **error_path) { @@ -1268,7 +1271,7 @@ int setup_namespace( _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_free_ void *root_hash_decoded = NULL; - _cleanup_free_ char *verity_data = NULL; + _cleanup_free_ char *verity_data = NULL, *hash_sig_path = NULL; MountEntry *m = NULL, *mounts = NULL; size_t n_mounts; bool require_prefix = false; @@ -1299,7 +1302,7 @@ int setup_namespace( if (r < 0) return log_debug_errno(r, "Failed to create loop device for root image: %m"); - r = verity_metadata_load(root_image, root_hash_path, root_hash ? NULL : &root_hash_decoded, root_hash ? NULL : &root_hash_size, root_verity ? NULL : &verity_data); + r = verity_metadata_load(root_image, root_hash_path, root_hash ? NULL : &root_hash_decoded, root_hash ? NULL : &root_hash_size, root_verity ? NULL : &verity_data, root_hash_sig || root_hash_sig_path ? NULL : &hash_sig_path); if (r < 0) return log_debug_errno(r, "Failed to load root hash: %m"); dissect_image_flags |= root_verity || verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; @@ -1308,7 +1311,7 @@ int setup_namespace( if (r < 0) return log_debug_errno(r, "Failed to dissect image: %m"); - r = dissected_image_decrypt(dissected_image, NULL, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, dissect_image_flags, &decrypted_image); + r = dissected_image_decrypt(dissected_image, NULL, root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, root_hash_sig_path ?: hash_sig_path, root_hash_sig, root_hash_sig_size, dissect_image_flags, &decrypted_image); if (r < 0) return log_debug_errno(r, "Failed to decrypt dissected image: %m"); } diff --git a/src/core/namespace.h b/src/core/namespace.h index a687be5bfd..b04b9b442e 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -91,6 +91,9 @@ int setup_namespace( const void *root_hash, size_t root_hash_size, const char *root_hash_path, + const void *root_hash_sig, + size_t root_hash_sig_size, + const char *root_hash_sig_path, const char *root_verity, DissectImageFlags dissected_image_flags, char **error_path); diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 2a8eaca5bd..66ac638401 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -28,9 +28,14 @@ static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DI static void *arg_root_hash = NULL; static char *arg_verity_data = NULL; static size_t arg_root_hash_size = 0; +static char *arg_root_hash_sig_path = NULL; +static void *arg_root_hash_sig = NULL; +static size_t arg_root_hash_sig_size = 0; STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep); static void help(void) { printf("%s [OPTIONS...] IMAGE\n" @@ -43,6 +48,10 @@ static void help(void) { " --fsck=BOOL Run fsck before mounting\n" " --discard=MODE Choose 'discard' mode (disabled, loop, all, crypto)\n" " --root-hash=HASH Specify root hash for verity\n" + " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" + " as a DER encoded PKCS7, either as a path to a file\n" + " or as an ASCII base64 encoded string prefixed by\n" + " 'base64:'\n" " --verity-data=PATH Specify data file with hash tree for verity if it is\n" " not embedded in IMAGE\n", program_invocation_short_name, @@ -57,17 +66,19 @@ static int parse_argv(int argc, char *argv[]) { ARG_ROOT_HASH, ARG_FSCK, ARG_VERITY_DATA, + ARG_ROOT_HASH_SIG, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "mount", no_argument, NULL, 'm' }, - { "read-only", no_argument, NULL, 'r' }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "mount", no_argument, NULL, 'm' }, + { "read-only", no_argument, NULL, 'r' }, + { "discard", required_argument, NULL, ARG_DISCARD }, + { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, + { "fsck", required_argument, NULL, ARG_FSCK }, + { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, + { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, {} }; @@ -140,6 +151,31 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_ROOT_HASH_SIG: { + char *value; + + if ((value = startswith(optarg, "base64:"))) { + void *p; + size_t l; + + r = unbase64mem(value, strlen(value), &p, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); + + free_and_replace(arg_root_hash_sig, p); + arg_root_hash_sig_size = l; + arg_root_hash_sig_path = mfree(arg_root_hash_sig_path); + } else { + r = parse_path_argument_and_warn(optarg, false, &arg_root_hash_sig_path); + if (r < 0) + return r; + arg_root_hash_sig = mfree(arg_root_hash_sig); + arg_root_hash_sig_size = 0; + } + + break; + } + case ARG_FSCK: r = parse_boolean(optarg); if (r < 0) @@ -202,7 +238,8 @@ static int run(int argc, char *argv[]) { return log_error_errno(r, "Failed to set up loopback device: %m"); r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size, - arg_verity_data ? NULL : &arg_verity_data); + arg_verity_data ? NULL : &arg_verity_data, + arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path); if (r < 0) return log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image); arg_flags |= arg_verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0; @@ -279,7 +316,7 @@ static int run(int argc, char *argv[]) { } case ACTION_MOUNT: - r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_flags, &di); + r = dissected_image_decrypt_interactively(m, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, arg_flags, &di); if (r < 0) return r; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 2f86d4094d..0f2d01c0aa 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -200,6 +200,9 @@ static unsigned long arg_clone_ns_flags = CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS static MountSettingsMask arg_mount_settings = MOUNT_APPLY_APIVFS_RO|MOUNT_APPLY_TMPFS_TMP; static void *arg_root_hash = NULL; static char *arg_verity_data = NULL; +static char *arg_root_hash_sig_path = NULL; +static void *arg_root_hash_sig = NULL; +static size_t arg_root_hash_sig_size = 0; static size_t arg_root_hash_size = 0; static char **arg_syscall_allow_list = NULL; static char **arg_syscall_deny_list = NULL; @@ -244,6 +247,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_property_message, sd_bus_message_unrefp); STATIC_DESTRUCTOR_REGISTER(arg_parameters, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root_hash, freep); STATIC_DESTRUCTOR_REGISTER(arg_verity_data, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root_hash_sig, freep); STATIC_DESTRUCTOR_REGISTER(arg_syscall_allow_list, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_syscall_deny_list, strv_freep); #if HAVE_SECCOMP @@ -305,6 +310,10 @@ static int help(void) { " --read-only Mount the root directory read-only\n" " --volatile[=MODE] Run the system in volatile mode\n" " --root-hash=HASH Specify verity root hash for root disk image\n" + " --root-hash-sig=SIG Specify pkcs7 signature of root hash for verity\n" + " as a DER encoded PKCS7, either as a path to a file\n" + " or as an ASCII base64 encoded string prefixed by\n" + " 'base64:'\n" " --verity-data=PATH Specify hash device for verity\n" " --pivot-root=PATH[:PATH]\n" " Pivot root to given directory in the container\n\n" @@ -667,6 +676,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_OCI_BUNDLE, ARG_NO_PAGER, ARG_VERITY_DATA, + ARG_ROOT_HASH_SIG, }; static const struct option options[] = { @@ -733,6 +743,7 @@ static int parse_argv(int argc, char *argv[]) { { "oci-bundle", required_argument, NULL, ARG_OCI_BUNDLE }, { "no-pager", no_argument, NULL, ARG_NO_PAGER }, { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, + { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, {} }; @@ -1327,6 +1338,31 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_ROOT_HASH_SIG: { + char *value; + + if ((value = startswith(optarg, "base64:"))) { + void *p; + size_t l; + + r = unbase64mem(value, strlen(value), &p, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash signature '%s': %m", optarg); + + free_and_replace(arg_root_hash_sig, p); + arg_root_hash_sig_size = l; + arg_root_hash_sig_path = mfree(arg_root_hash_sig_path); + } else { + r = parse_path_argument_and_warn(optarg, false, &arg_root_hash_sig_path); + if (r < 0) + return r; + arg_root_hash_sig = mfree(arg_root_hash_sig); + arg_root_hash_sig_size = 0; + } + + break; + } + case ARG_SYSTEM_CALL_FILTER: { bool negative; const char *items; @@ -5143,7 +5179,8 @@ static int run(int argc, char *argv[]) { } r = verity_metadata_load(arg_image, NULL, arg_root_hash ? NULL : &arg_root_hash, &arg_root_hash_size, - arg_verity_data ? NULL : &arg_verity_data); + arg_verity_data ? NULL : &arg_verity_data, + arg_root_hash_sig_path || arg_root_hash_sig ? NULL : &arg_root_hash_sig_path); if (r < 0) { log_error_errno(r, "Failed to read verity artefacts for %s: %m", arg_image); goto finish; @@ -5193,7 +5230,7 @@ static int run(int argc, char *argv[]) { if (!arg_root_hash && dissected_image->can_verity) log_notice("Note: image %s contains verity information, but no root hash specified! Proceeding without integrity checking.", arg_image); - r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, 0, &decrypted_image); + r = dissected_image_decrypt_interactively(dissected_image, NULL, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_root_hash_sig_path, arg_root_hash_sig, arg_root_hash_sig_size, 0, &decrypted_image); if (r < 0) goto finish; diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 6cfac82146..f2652ed9a5 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1434,6 +1434,26 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_byte_array(m, field, roothash_decoded, roothash_decoded_size); } + if (streq(field, "RootHashSignature")) { + _cleanup_free_ void *roothash_sig_decoded = NULL; + char *value; + size_t roothash_sig_decoded_size = 0; + + /* We have the path to a roothash signature to load and decode, eg: RootHash=/foo/bar.roothash.p7s */ + if (path_is_absolute(eq)) + return bus_append_string(m, "RootHashSignaturePath", eq); + + if (!(value = startswith(eq, "base64:"))) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to decode RootHashSignature= '%s', not a path but doesn't start with 'base64:': %m", eq); + + /* We have a roothash signature to decode, eg: RootHashSignature=base64:012345789abcdef */ + r = unbase64mem(value, strlen(value), &roothash_sig_decoded, &roothash_sig_decoded_size); + if (r < 0) + return log_error_errno(r, "Failed to decode RootHashSignature= '%s': %m", eq); + + return bus_append_byte_array(m, field, roothash_sig_decoded, roothash_sig_decoded_size); + } + return 0; } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index e576518c6b..fdf4d481f6 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -1223,6 +1223,9 @@ static int verity_partition( const void *root_hash, size_t root_hash_size, const char *verity_data, + const char *root_hash_sig_path, + const void *root_hash_sig, + size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage *d) { @@ -1267,7 +1270,25 @@ static int verity_partition( if (r < 0) return r; - r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY); + if (root_hash_sig || root_hash_sig_path) { +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + if (root_hash_sig) + r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, root_hash_sig, root_hash_sig_size, CRYPT_ACTIVATE_READONLY); + else { + _cleanup_free_ char *hash_sig = NULL; + size_t hash_sig_size; + + r = read_full_file_full(AT_FDCWD, root_hash_sig_path, 0, &hash_sig, &hash_sig_size); + if (r < 0) + return r; + + r = crypt_activate_by_signed_key(cd, name, root_hash, root_hash_size, hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY); + } +#else + r = log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()"); +#endif + } else + r = crypt_activate_by_volume_key(cd, name, root_hash, root_hash_size, CRYPT_ACTIVATE_READONLY); if (r < 0) return r; @@ -1287,6 +1308,9 @@ int dissected_image_decrypt( const void *root_hash, size_t root_hash_size, const char *verity_data, + const char *root_hash_sig_path, + const void *root_hash_sig, + size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret) { @@ -1333,7 +1357,7 @@ int dissected_image_decrypt( k = PARTITION_VERITY_OF(i); if (k >= 0) { - r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, flags, d); + r = verity_partition(p, m->partitions + k, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags, d); if (r < 0) return r; } @@ -1359,6 +1383,9 @@ int dissected_image_decrypt_interactively( const void *root_hash, size_t root_hash_size, const char *verity_data, + const char *root_hash_sig_path, + const void *root_hash_sig, + size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret) { @@ -1369,7 +1396,7 @@ int dissected_image_decrypt_interactively( n--; for (;;) { - r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, verity_data, flags, ret); + r = dissected_image_decrypt(m, passphrase, root_hash, root_hash_size, verity_data, root_hash_sig_path, root_hash_sig, root_hash_sig_size, flags, ret); if (r >= 0) return r; if (r == -EKEYREJECTED) @@ -1421,8 +1448,8 @@ int decrypted_image_relinquish(DecryptedImage *d) { return 0; } -int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data) { - _cleanup_free_ char *verity_filename = NULL; +int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig) { + _cleanup_free_ char *verity_filename = NULL, *roothashsig_filename = NULL; _cleanup_free_ void *roothash_decoded = NULL; size_t roothash_decoded_size = 0; int r; @@ -1437,6 +1464,8 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r *ret_roothash_size = 0; if (ret_verity_data) *ret_verity_data = NULL; + if (ret_roothashsig) + *ret_roothashsig = NULL; return 0; } @@ -1461,6 +1490,29 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r } } + if (ret_roothashsig) { + char *e; + + /* Follow naming convention recommended by the relevant RFC: + * https://tools.ietf.org/html/rfc5751#section-3.2.1 */ + roothashsig_filename = new(char, strlen(image) + STRLEN(".roothash.p7s") + 1); + if (!roothashsig_filename) + return -ENOMEM; + strcpy(roothashsig_filename, image); + e = endswith(roothashsig_filename, ".raw"); + if (e) + strcpy(e, ".roothash.p7s"); + else + strcat(roothashsig_filename, ".roothash.p7s"); + + r = access(roothashsig_filename, R_OK); + if (r < 0) { + if (errno != ENOENT) + return -errno; + roothashsig_filename = mfree(roothashsig_filename); + } + } + if (ret_roothash) { _cleanup_free_ char *text = NULL; assert(ret_roothash_size); @@ -1507,6 +1559,8 @@ int verity_metadata_load(const char *image, const char *root_hash_path, void **r } if (ret_verity_data) *ret_verity_data = TAKE_PTR(verity_filename); + if (roothashsig_filename) + *ret_roothashsig = TAKE_PTR(roothashsig_filename); return 1; } diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 6a53b94948..c7d078f4b7 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -87,8 +87,8 @@ int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); -int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret); -int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret); +int dissected_image_decrypt_interactively(DissectedImage *m, const char *passphrase, const void *root_hash, size_t root_hash_size, const char *verity_data, const char *root_hash_sig_path, const void *root_hash_sig, size_t root_hash_sig_size, DissectImageFlags flags, DecryptedImage **ret); int dissected_image_mount(DissectedImage *m, const char *dest, uid_t uid_shift, DissectImageFlags flags); int dissected_image_acquire_metadata(DissectedImage *m); @@ -100,6 +100,6 @@ int decrypted_image_relinquish(DecryptedImage *d); const char* partition_designator_to_string(int i) _const_; int partition_designator_from_string(const char *name) _pure_; -int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data); +int verity_metadata_load(const char *image, const char *root_hash_path, void **ret_roothash, size_t *ret_roothash_size, char **ret_verity_data, char **ret_roothashsig); bool dissected_image_can_do_verity(const DissectedImage *image, unsigned partition_designator); bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_designator); diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 4d793d75e2..678982c61c 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -5188,7 +5188,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m return 1; - } else if (contents[0] == SD_BUS_TYPE_BYTE && streq(name, "StandardInputData")) { + } else if (contents[0] == SD_BUS_TYPE_BYTE && STR_IN_SET(name, "StandardInputData", "RootHashSignature")) { _cleanup_free_ char *h = NULL; const void *p; size_t sz; diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index d0b4ec2764..ad135e146f 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -157,6 +157,9 @@ static void test_protect_kernel_logs(void) { NULL, NULL, 0, + NULL, + NULL, + 0, NULL); assert_se(r == 0); diff --git a/src/test/test-ns.c b/src/test/test-ns.c index ba2c2ed53b..cbc41b7a38 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -81,6 +81,9 @@ int main(int argc, char *argv[]) { NULL, NULL, 0, + NULL, + NULL, + 0, NULL); if (r < 0) { log_error_errno(r, "Failed to set up namespace: %m"); diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c index 9c2fe9a1b4..465d194b40 100644 --- a/src/veritysetup/veritysetup.c +++ b/src/veritysetup/veritysetup.c @@ -6,9 +6,11 @@ #include "alloc-util.h" #include "crypt-util.h" +#include "fileio.h" #include "hexdecoct.h" #include "log.h" #include "main-func.h" +#include "path-util.h" #include "pretty-print.h" #include "string-util.h" #include "terminal-util.h" @@ -29,7 +31,7 @@ static int help(void) { if (r < 0) return log_oom(); - printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH\n" + printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH [ROOTHASHSIG]\n" "%s detach VOLUME\n\n" "Attaches or detaches an integrity protected block device.\n" "\nSee the %s for details.\n" @@ -87,7 +89,28 @@ static int run(int argc, char *argv[]) { if (r < 0) return log_error_errno(r, "Failed to configure data device: %m"); - r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY); + if (argc > 6) { +#if HAVE_CRYPT_ACTIVATE_BY_SIGNED_KEY + _cleanup_free_ char *hash_sig = NULL; + size_t hash_sig_size; + char *value; + + if ((value = startswith(argv[6], "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", argv[6]); + } else { + r = read_full_file_full(AT_FDCWD, argv[6], 0, &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, argv[2], m, l, hash_sig, hash_sig_size, CRYPT_ACTIVATE_READONLY); +#else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "activation of verity device with signature %s requested, but not supported by cryptsetup due to missing crypt_activate_by_signed_key()", argv[6]); +#endif + } else + r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY); if (r < 0) return log_error_errno(r, "Failed to set up verity device: %m"); diff --git a/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 492d2a033b..dbff9ab2cc 100644 --- a/test/fuzz/fuzz-unit-file/directives.service +++ b/test/fuzz/fuzz-unit-file/directives.service @@ -197,6 +197,7 @@ RootDirectory= RootDirectoryStartOnly= RootImage= RootHash= +RootHashSignature= RootVerity= RuntimeMaxSec= SELinuxContextFromNet=