From 18d73705874f9bf0643485714e9dc069a2e9b599 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Mon, 29 Jun 2020 13:19:31 +0100 Subject: [PATCH] service: add new RootImageOptions feature Allows to specify mount options for RootImage. In case of multi-partition images, the partition number can be prefixed followed by colon. Eg: RootImageOptions=1:ro,dev 2:nosuid nodev In absence of a partition number, 0 is assumed. --- man/systemd.exec.xml | 13 +++ src/core/dbus-execute.c | 88 ++++++++++++++++++++ src/core/execute.c | 13 ++- src/core/execute.h | 1 + src/core/load-fragment-gperf.gperf.m4 | 1 + src/core/load-fragment.c | 91 +++++++++++++++++++++ src/core/load-fragment.h | 1 + src/core/namespace.c | 2 + src/core/namespace.h | 1 + src/dissect/dissect.c | 2 +- src/firstboot/firstboot.c | 2 +- src/gpt-auto-generator/gpt-auto-generator.c | 2 +- src/nspawn/nspawn.c | 1 + src/portable/portable.c | 2 +- src/shared/bus-unit-util.c | 68 +++++++++++++++ src/shared/dissect-image.c | 75 +++++++++++++++-- src/shared/dissect-image.h | 17 +++- src/shared/machine-image.c | 2 +- src/test/test-dissect-image.c | 2 +- src/test/test-namespace.c | 1 + src/test/test-ns.c | 1 + test/TEST-50-DISSECT/test.sh | 1 + test/units/testsuite-50.sh | 31 +++++++ 23 files changed, 403 insertions(+), 15 deletions(-) diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 3618b52808e..c5d755e8976 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -145,6 +145,19 @@ + + RootImageOptions= + + Takes a comma-separated list of mount options that will be used on disk images specified by + RootImage=. Optionally a partition number can be prefixed, followed by colon, in + case the image has multiple partitions, otherwise partition number 0 is implied. + Options for multiple partitions can be specified in a single line with space separators. Assigning an empty + string removes previous assignments. For a list of valid mount options, please refer to + mount8. + + + + RootHash= diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 50f7ada8cef..49729799abf 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -784,6 +784,37 @@ static int property_get_root_hash_sig( return sd_bus_message_append_array(reply, 'y', c->root_hash_sig, c->root_hash_sig_size); } +static int property_get_root_image_options( + 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; + MountOptions *m; + int r; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(us)"); + if (r < 0) + return r; + + LIST_FOREACH(mount_options, m, c->root_image_options) { + r = sd_bus_message_append(reply, "(us)", m->partition_number, m->options); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + 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), @@ -826,6 +857,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RootImage", "s", NULL, offsetof(ExecContext, root_image), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootImageOptions", "a(us)", property_get_root_image_options, 0, 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), @@ -1301,6 +1333,62 @@ int bus_exec_context_set_transient_property( if (streq(name, "RootImage")) return bus_set_transient_path(u, name, &c->root_image, message, flags, error); + if (streq(name, "RootImageOptions")) { + _cleanup_(mount_options_free_allp) MountOptions *options = NULL; + _cleanup_free_ char *format_str = NULL; + const char *mount_options; + unsigned partition_number; + + r = sd_bus_message_enter_container(message, 'a', "(us)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(us)", &partition_number, &mount_options)) > 0) { + _cleanup_free_ char *previous = TAKE_PTR(format_str); + _cleanup_free_ MountOptions *o = NULL; + + if (chars_intersect(mount_options, WHITESPACE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, + "Invalid mount options string, contains whitespace character(s): %s", mount_options); + + if (asprintf(&format_str, "%s%s%u:%s", strempty(previous), previous ? " " : "", partition_number, mount_options) < 0) + return -ENOMEM; + + o = new(MountOptions, 1); + if (!o) + return -ENOMEM; + *o = (MountOptions) { + .partition_number = partition_number, + .options = strdup(mount_options), + }; + if (!o->options) + return -ENOMEM; + LIST_APPEND(mount_options, options, TAKE_PTR(o)); + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (LIST_IS_EMPTY(options)) { + c->root_image_options = mount_options_free_all(c->root_image_options); + unit_write_settingf(u, flags, name, "%s=", name); + } else { + LIST_JOIN(mount_options, c->root_image_options, options); + unit_write_settingf( + u, flags|UNIT_ESCAPE_SPECIFIERS, name, + "%s=%s", + name, + format_str); + } + } + + return 1; + } + if (streq(name, "RootHash")) { const void *roothash_decoded; size_t roothash_decoded_size; diff --git a/src/core/execute.c b/src/core/execute.c index 2a4840a3a9b..39ffcba5802 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2660,7 +2660,7 @@ static int apply_mount_namespace( if (context->mount_flags == MS_SHARED) log_unit_debug(u, "shared mount propagation hidden by other fs namespacing unit settings: ignoring"); - r = setup_namespace(root_dir, root_image, + r = setup_namespace(root_dir, root_image, context->root_image_options, &ns_info, context->read_write_paths, needs_sandboxing ? context->read_only_paths : NULL, needs_sandboxing ? context->inaccessible_paths : NULL, @@ -4207,6 +4207,7 @@ void exec_context_done(ExecContext *c) { c->working_directory = mfree(c->working_directory); c->root_directory = mfree(c->root_directory); c->root_image = mfree(c->root_image); + c->root_image_options = mount_options_free_all(c->root_image_options); c->root_hash = mfree(c->root_hash); c->root_hash_size = 0; c->root_hash_path = mfree(c->root_hash_path); @@ -4618,6 +4619,16 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { if (c->root_image) fprintf(f, "%sRootImage: %s\n", prefix, c->root_image); + if (c->root_image_options) { + MountOptions *o; + + fprintf(f, "%sRootImageOptions:", prefix); + LIST_FOREACH(mount_options, o, c->root_image_options) + if (!isempty(o->options)) + fprintf(f, " %u:%s", o->partition_number, o->options); + fprintf(f, "\n"); + } + if (c->root_hash) { _cleanup_free_ char *encoded = NULL; encoded = hexmem(c->root_hash, c->root_hash_size); diff --git a/src/core/execute.h b/src/core/execute.h index fc7bc5c24b2..349f583c1a6 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -158,6 +158,7 @@ struct ExecContext { 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; + LIST_HEAD(MountOptions, root_image_options); 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 12ae78eb7dc..a7c9bd9f711 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -23,6 +23,7 @@ m4_define(`EXEC_CONTEXT_CONFIG_ITEMS', `$1.WorkingDirectory, config_parse_working_directory, 0, offsetof($1, exec_context) $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.RootImageOptions, config_parse_root_image_options, 0, offsetof($1, exec_context) $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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 3036aa8ba44..2a2a5af58fd 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -1416,6 +1416,97 @@ int config_parse_exec_cpu_sched_prio(const char *unit, return 0; } +int config_parse_root_image_options( + 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_(mount_options_free_allp) MountOptions *options = NULL; + ExecContext *c = data; + const Unit *u = userdata; + const char *p = rvalue; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->root_image_options = mount_options_free_all(c->root_image_options); + return 0; + } + + for (;;) { + _cleanup_free_ char *mount_options_resolved = NULL, *first = NULL, *tuple = NULL; + const char *mount_options = NULL, *second = NULL; + MountOptions *o = NULL; + unsigned int partition_number = 0; + + r = extract_first_word(&p, &tuple, WHITESPACE, EXTRACT_UNQUOTE); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue); + return 0; + } + + second = tuple; + r = extract_first_word(&second, &first, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == 0) + continue; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s: %s", lvalue, rvalue); + continue; + } + + /* Format is either '0:foo' or 'foo' (0 is implied) */ + if (!isempty(second) && second[-1] == ':') { + mount_options = second; + r = safe_atou(first, &partition_number); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse partition number from \"%s\", ignoring: %m", first); + continue; + } + } else + mount_options = first; + + r = unit_full_printf(u, mount_options, &mount_options_resolved); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", mount_options); + continue; + } + + o = new(MountOptions, 1); + if (!o) + return log_oom(); + *o = (MountOptions) { + .partition_number = partition_number, + .options = TAKE_PTR(mount_options_resolved), + }; + LIST_APPEND(mount_options, options, o); + } + + /* empty spaces/separators only */ + if (LIST_IS_EMPTY(options)) + c->root_image_options = mount_options_free_all(c->root_image_options); + else + LIST_JOIN(mount_options, c->root_image_options, options); + + return 0; +} + int config_parse_exec_root_hash( const char *unit, const char *filename, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index ac3940a1b7f..253de9467f0 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -44,6 +44,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_cpu_sched_policy); 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_root_image_options); CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash); CONFIG_PARSER_PROTOTYPE(config_parse_exec_root_hash_sig); CONFIG_PARSER_PROTOTYPE(config_parse_capability_set); diff --git a/src/core/namespace.c b/src/core/namespace.c index 5865dfe93ea..8b5efa82521 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -1259,6 +1259,7 @@ static bool home_read_only( int setup_namespace( const char* root_directory, const char* root_image, + const MountOptions *root_image_options, const NamespaceInfo *ns_info, char** read_write_paths, char** read_only_paths, @@ -1333,6 +1334,7 @@ int setup_namespace( root_hash ?: root_hash_decoded, root_hash_size, root_verity ?: verity_data, + root_image_options, dissect_image_flags, &dissected_image); if (r < 0) diff --git a/src/core/namespace.h b/src/core/namespace.h index b182223bd41..258bd7c1314 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -75,6 +75,7 @@ struct TemporaryFileSystem { int setup_namespace( const char *root_directory, const char *root_image, + const MountOptions *root_image_options, const NamespaceInfo *ns_info, char **read_write_paths, char **read_only_paths, diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 66ac638401c..318cd37c6f2 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -244,7 +244,7 @@ static int run(int argc, char *argv[]) { 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; - r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, arg_flags, &m); + r = dissect_image_and_warn(d->fd, arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, NULL, arg_flags, &m); if (r < 0) return r; diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index a3f442518ec..78abcbeff6c 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -933,7 +933,7 @@ static int setup_image(char **ret_mount_dir, LoopDevice **ret_loop_device, Decry if (r < 0) return log_error_errno(r, "Failed to set up loopback device: %m"); - r = dissect_image_and_warn(d->fd, arg_image, NULL, 0, NULL, f, &dissected_image); + r = dissect_image_and_warn(d->fd, arg_image, NULL, 0, NULL, NULL, f, &dissected_image); if (r < 0) return r; diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index a9478b9dbd8..02d8837ca9b 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -665,7 +665,7 @@ static int enumerate_partitions(dev_t devnum) { if (r <= 0) return r; - r = dissect_image(fd, NULL, 0, NULL, DISSECT_IMAGE_GPT_ONLY|DISSECT_IMAGE_NO_UDEV, &m); + r = dissect_image(fd, NULL, 0, NULL, NULL, DISSECT_IMAGE_GPT_ONLY|DISSECT_IMAGE_NO_UDEV, &m); if (r == -ENOPKG) { log_debug_errno(r, "No suitable partition table found, ignoring."); return 0; diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3b9493f232e..a1613f076d9 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -5223,6 +5223,7 @@ static int run(int argc, char *argv[]) { arg_image, arg_root_hash, arg_root_hash_size, arg_verity_data, + NULL, dissect_image_flags, &dissected_image); if (r == -ENOPKG) { diff --git a/src/portable/portable.c b/src/portable/portable.c index 3a1367ec2b0..bd7edce8075 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -380,7 +380,7 @@ static int portable_extract_by_path( if (r < 0) return log_debug_errno(r, "Failed to create temporary directory: %m"); - r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, NULL, DISSECT_IMAGE_READ_ONLY|DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r == -ENOPKG) sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Couldn't identify a suitable partition table or file system in '%s'.", path); else if (r == -EADDRNOTAVAIL) diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index f2652ed9a59..30a872342ff 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1454,6 +1454,74 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_byte_array(m, field, roothash_sig_decoded, roothash_sig_decoded_size); } + if (streq(field, "RootImageOptions")) { + const char *p = eq; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', "a(us)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(us)"); + if (r < 0) + return bus_log_create_error(r); + + for (;;) { + _cleanup_free_ char *first = NULL, *tuple = NULL; + const char *mount_options = NULL, *second = NULL; + unsigned partition_number = 0; + + r = extract_first_word(&p, &tuple, WHITESPACE, EXTRACT_UNQUOTE); + if (r == 0) + break; + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %m"); + + second = tuple; + r = extract_first_word(&second, &first, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == 0) + continue; + if (r < 0) + return log_error_errno(r, "Failed to parse argument: %m"); + + /* Format is either '0:foo' or 'foo' (0 is implied) */ + if (!isempty(second) && second[-1] == ':') { + mount_options = second; + r = safe_atou(first, &partition_number); + if (r < 0) { + log_error_errno(r, "Failed to parse partition number from %s: %m", first); + continue; + } + } else + mount_options = first; + + r = sd_bus_message_append(m, "(us)", partition_number, mount_options); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 1; + } + return 0; } diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 24be6de6c54..e96658ca669 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -308,6 +308,7 @@ int dissect_image( const void *root_hash, size_t root_hash_size, const char *verity_data, + const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret) { @@ -400,8 +401,8 @@ int dissect_image( (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { - _cleanup_free_ char *t = NULL, *n = NULL; - const char *fstype = NULL; + _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL; + const char *fstype = NULL, *options = NULL; /* OK, we have found a file system, that's our root partition then. */ (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); @@ -420,6 +421,13 @@ int dissect_image( m->verity = root_hash && verity_data; m->can_verity = !!verity_data; + options = mount_options_from_part(mount_options, 0); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_ROOT] = (DissectedPartition) { .found = true, .rw = !m->verity, @@ -427,6 +435,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .fstype = TAKE_PTR(t), .node = TAKE_PTR(n), + .mount_options = TAKE_PTR(o), }; m->encrypted = streq_ptr(fstype, "crypto_LUKS"); @@ -691,7 +700,8 @@ int dissect_image( } if (designator != _PARTITION_DESIGNATOR_INVALID) { - _cleanup_free_ char *t = NULL, *n = NULL; + _cleanup_free_ char *t = NULL, *n = NULL, *o = NULL; + const char *options = NULL; /* First one wins */ if (m->partitions[designator].found) @@ -707,6 +717,13 @@ int dissect_image( if (!n) return -ENOMEM; + options = mount_options_from_part(mount_options, nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[designator] = (DissectedPartition) { .found = true, .partno = nr, @@ -715,6 +732,7 @@ int dissect_image( .node = TAKE_PTR(n), .fstype = TAKE_PTR(t), .uuid = id, + .mount_options = TAKE_PTR(o), }; } @@ -740,9 +758,9 @@ int dissect_image( break; case 0xEA: { /* Boot Loader Spec extended $BOOT partition */ - _cleanup_free_ char *n = NULL; + _cleanup_free_ char *n = NULL, *o = NULL; sd_id128_t id = SD_ID128_NULL; - const char *sid; + const char *sid, *options = NULL; /* First one wins */ if (m->partitions[PARTITION_XBOOTLDR].found) @@ -756,6 +774,13 @@ int dissect_image( if (!n) return -ENOMEM; + options = mount_options_from_part(mount_options, nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_XBOOTLDR] = (DissectedPartition) { .found = true, .partno = nr, @@ -763,6 +788,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .node = TAKE_PTR(n), .uuid = id, + .mount_options = TAKE_PTR(o), }; break; @@ -785,6 +811,8 @@ int dissect_image( zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); } else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) { + _cleanup_free_ char *o = NULL; + const char *options = NULL; /* If the root has was set, then we won't fallback to a generic node, because the root hash * decides */ @@ -800,6 +828,13 @@ int dissect_image( if (multiple_generic) return -ENOTUNIQ; + options = mount_options_from_part(mount_options, generic_nr); + if (options) { + o = strdup(options); + if (!o) + return -ENOMEM; + } + m->partitions[PARTITION_ROOT] = (DissectedPartition) { .found = true, .rw = generic_rw, @@ -807,6 +842,7 @@ int dissect_image( .architecture = _ARCHITECTURE_INVALID, .node = TAKE_PTR(generic_node), .uuid = generic_uuid, + .mount_options = TAKE_PTR(o), }; } } @@ -869,6 +905,7 @@ DissectedImage* dissected_image_unref(DissectedImage *m) { free(m->partitions[i].node); free(m->partitions[i].decrypted_fstype); free(m->partitions[i].decrypted_node); + free(m->partitions[i].mount_options); } free(m->hostname); @@ -1008,6 +1045,10 @@ static int mount_partition( return -ENOMEM; } + if (!isempty(m->mount_options)) + if (!strextend_with_separator(&options, ",", m->mount_options, NULL)) + return -ENOMEM; + r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); if (r < 0) return r; @@ -1819,6 +1860,7 @@ int dissect_image_and_warn( const void *root_hash, size_t root_hash_size, const char *verity_data, + const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret) { @@ -1833,7 +1875,7 @@ int dissect_image_and_warn( name = buffer; } - r = dissect_image(fd, root_hash, root_hash_size, verity_data, flags, ret); + r = dissect_image(fd, root_hash, root_hash_size, verity_data, mount_options, flags, ret); switch (r) { @@ -1880,6 +1922,27 @@ bool dissected_image_has_verity(const DissectedImage *image, unsigned partition_ return k >= 0 && image->partitions[k].found; } +MountOptions* mount_options_free_all(MountOptions *options) { + MountOptions *m; + + while ((m = options)) { + LIST_REMOVE(mount_options, options, m); + free(m->options); + free(m); + } + + return NULL; +} + +const char* mount_options_from_part(const MountOptions *options, unsigned int partition_number) { + MountOptions *m; + + LIST_FOREACH(mount_options, m, (MountOptions *)options) + if (partition_number == m->partition_number && !isempty(m->options)) + return m->options; + return NULL; +} + static const char *const partition_designator_table[] = { [PARTITION_ROOT] = "root", [PARTITION_ROOT_SECONDARY] = "root-secondary", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 84ec1ce3311..52aa377a671 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -5,11 +5,13 @@ #include "sd-id128.h" +#include "list.h" #include "macro.h" typedef struct DissectedImage DissectedImage; typedef struct DissectedPartition DissectedPartition; typedef struct DecryptedImage DecryptedImage; +typedef struct MountOptions MountOptions; struct DissectedPartition { bool found:1; @@ -21,6 +23,7 @@ struct DissectedPartition { char *node; char *decrypted_node; char *decrypted_fstype; + char *mount_options; }; enum { @@ -81,9 +84,19 @@ struct DissectedImage { char **os_release; }; +struct MountOptions { + unsigned partition_number; + char *options; + LIST_FIELDS(MountOptions, mount_options); +}; + +MountOptions* mount_options_free_all(MountOptions *options); +DEFINE_TRIVIAL_CLEANUP_FUNC(MountOptions*, mount_options_free_all); +const char* mount_options_from_part(const MountOptions *options, unsigned int partition_number); + int probe_filesystem(const char *node, char **ret_fstype); -int dissect_image(int fd, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DissectedImage **ret); -int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size_t root_hash_size, const char *verity_data, DissectImageFlags flags, DissectedImage **ret); +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, const char *verity_data, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret); +int dissect_image_and_warn(int fd, const char *name, const void *root_hash, size_t root_hash_size, const char *verity_data, const MountOptions *mount_options, DissectImageFlags flags, DissectedImage **ret); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 1b7cfb5028a..c6ff41898b2 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -1171,7 +1171,7 @@ int image_read_metadata(Image *i) { if (r < 0) return r; - r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r < 0) return r; diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c index 13ff8add5da..fe78216b781 100644 --- a/src/test/test-dissect-image.c +++ b/src/test/test-dissect-image.c @@ -28,7 +28,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = dissect_image(d->fd, NULL, 0, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); + r = dissect_image(d->fd, NULL, 0, NULL, NULL, DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_RELAX_VAR_CHECK, &m); if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); return EXIT_FAILURE; diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index a7ad4828370..95021ee7bf7 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -150,6 +150,7 @@ static void test_protect_kernel_logs(void) { assert_se(fd > 0); r = setup_namespace(NULL, + NULL, NULL, &ns_info, NULL, diff --git a/src/test/test-ns.c b/src/test/test-ns.c index cbc41b7a385..ced287dd6e0 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -62,6 +62,7 @@ int main(int argc, char *argv[]) { log_info("Not chrooted"); r = setup_namespace(root_directory, + NULL, NULL, &ns_info, (char **) writable, diff --git a/test/TEST-50-DISSECT/test.sh b/test/TEST-50-DISSECT/test.sh index 38826580538..00f51479f40 100755 --- a/test/TEST-50-DISSECT/test.sh +++ b/test/TEST-50-DISSECT/test.sh @@ -34,6 +34,7 @@ test_create_image() { BASICTOOLS=( bash cat + mount ) oldinitdir=$initdir export initdir=$TESTDIR/minimal diff --git a/test/units/testsuite-50.sh b/test/units/testsuite-50.sh index 81e48e0ad19..28144b378fd 100755 --- a/test/units/testsuite-50.sh +++ b/test/units/testsuite-50.sh @@ -124,6 +124,37 @@ umount ${image_dir}/mount systemd-run -t --property RootImage=${image}.gpt --property RootHash=${roothash} /usr/bin/cat /usr/lib/os-release | grep -q -F "MARKER=1" +systemd-run -t --property RootImage=${image}.raw --property RootImageOptions="1:ro,noatime 2:ro,dev nosuid,dev" --property MountAPIVFS=yes mount | grep -F "squashfs" | grep -q -F "nosuid" +systemd-run -t --property RootImage=${image}.gpt --property RootImageOptions="1:ro,noatime 1:ro,dev" --property MountAPIVFS=yes mount | grep -F "squashfs" | grep -q -F "noatime" + +cat > /run/systemd/system/testservice-50a.service < /run/systemd/system/testservice-50b.service < %foo +busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/testservice_2d50b_2eservice org.freedesktop.systemd1.Service RootImageOptions | grep -F "nosuid,dev,%foo" + echo OK > /testok exit 0 -- 2.39.2