From d4d55b0d13e9326fccd566789cadf41308c5ddb8 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 8 Jun 2020 14:02:55 +0100 Subject: [PATCH] core: add RootHashSignature service parameter Allow to explicitly pass root hash signature as a unit option. Takes precedence over implicit checks. --- man/systemd.exec.xml | 14 +++++ src/core/dbus-execute.c | 68 +++++++++++++++++++++ src/core/execute.c | 18 +++++- src/core/execute.h | 6 +- src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/load-fragment.c | 60 ++++++++++++++++++ src/core/load-fragment.h | 1 + src/core/namespace.c | 7 ++- src/core/namespace.h | 3 + src/shared/bus-unit-util.c | 20 ++++++ src/systemctl/systemctl.c | 2 +- src/test/test-namespace.c | 3 + src/test/test-ns.c | 3 + test/fuzz/fuzz-unit-file/directives.service | 1 + 14 files changed, 200 insertions(+), 7 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index de0cfac2a9a..c828109d01a 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/src/core/dbus-execute.c b/src/core/dbus-execute.c index 45b5c0c13aa..41d64e80047 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 0765f112fd0..4bee1b19665 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 805709f2aa9..6bd71c17404 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 9cf959edd57..12ae78eb7dc 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 09065ccb366..0445a3a2c95 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 f0e109da3ac..ac3940a1b7f 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 a9985c56572..785a4694789 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) { @@ -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, &hash_sig_path); + 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, hash_sig_path, NULL, 0, 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 a687be5bfda..b04b9b442ea 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/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 6cfac821468..f2652ed9a59 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/systemctl/systemctl.c b/src/systemctl/systemctl.c index 4d793d75e20..678982c61c9 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 d0b4ec27644..ad135e146fe 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 ba2c2ed53b7..cbc41b7a385 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/test/fuzz/fuzz-unit-file/directives.service b/test/fuzz/fuzz-unit-file/directives.service index 492d2a033bb..dbff9ab2cc7 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= -- 2.39.2