diff options
author | Luca Boccassi <luca.boccassi@microsoft.com> | 2021-02-22 12:20:33 +0000 |
---|---|---|
committer | Luca Boccassi <luca.boccassi@microsoft.com> | 2021-02-23 15:34:46 +0000 |
commit | 93f597013a82298c5922f2f06de98be22b635e7b (patch) | |
tree | 963d5f91e083841912ab1a9eba67a96acbd81951 /src | |
parent | 82fb2da21347b750b3de53cde588ee1189f7acb7 (diff) | |
download | systemd-93f597013a82298c5922f2f06de98be22b635e7b.tar.gz |
Add ExtensionImages directive to form overlays
Add support for overlaying images for services on top of their
root fs, using a read-only overlay.
Diffstat (limited to 'src')
-rw-r--r-- | src/core/dbus-execute.c | 145 | ||||
-rw-r--r-- | src/core/execute.c | 19 | ||||
-rw-r--r-- | src/core/execute.h | 2 | ||||
-rw-r--r-- | src/core/load-fragment-gperf.gperf.m4 | 1 | ||||
-rw-r--r-- | src/core/load-fragment.c | 142 | ||||
-rw-r--r-- | src/core/load-fragment.h | 1 | ||||
-rw-r--r-- | src/core/namespace.c | 196 | ||||
-rw-r--r-- | src/core/namespace.h | 12 | ||||
-rw-r--r-- | src/shared/bus-unit-util.c | 104 | ||||
-rw-r--r-- | src/shared/dissect-image.c | 34 | ||||
-rw-r--r-- | src/shared/dissect-image.h | 2 | ||||
-rw-r--r-- | src/shared/mount-util.c | 2 | ||||
-rw-r--r-- | src/test/test-namespace.c | 2 | ||||
-rw-r--r-- | src/test/test-ns.c | 2 |
14 files changed, 651 insertions, 13 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 1f0e27a141..a4817ca6de 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -996,6 +996,60 @@ static int property_get_mount_images( return sd_bus_message_close_container(reply); } +static int property_get_extension_images( + 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; + int r; + + assert(bus); + assert(c); + assert(property); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(sba(ss))"); + if (r < 0) + return r; + + for (size_t i = 0; i < c->n_extension_images; i++) { + MountOptions *m; + + r = sd_bus_message_open_container(reply, SD_BUS_TYPE_STRUCT, "sba(ss)"); + if (r < 0) + return r; + r = sd_bus_message_append( + reply, "sb", + c->extension_images[i].source, + c->extension_images[i].ignore_enoent); + if (r < 0) + return r; + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + LIST_FOREACH(mount_options, m, c->extension_images[i].mount_options) { + r = sd_bus_message_append(reply, "(ss)", + partition_designator_to_string(m->partition_designator), + m->options); + if (r < 0) + return r; + } + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + r = sd_bus_message_close_container(reply); + 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), @@ -1044,6 +1098,7 @@ const sd_bus_vtable bus_exec_vtable[] = { 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("ExtensionImages", "a(sba(ss))", property_get_extension_images, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("MountImages", "a(ssba(ss))", property_get_mount_images, 0, 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), @@ -3356,6 +3411,7 @@ int bus_exec_context_set_transient_property( .destination = destination, .mount_options = options, .ignore_enoent = permissive, + .type = MOUNT_IMAGE_DISCRETE, }); if (r < 0) return r; @@ -3390,6 +3446,95 @@ int bus_exec_context_set_transient_property( mount_images = mount_image_free_many(mount_images, &n_mount_images); return 1; + } else if (streq(name, "ExtensionImages")) { + _cleanup_free_ char *format_str = NULL; + MountImage *extension_images = NULL; + size_t n_extension_images = 0; + + r = sd_bus_message_enter_container(message, 'a', "(sba(ss))"); + if (r < 0) + return r; + + for (;;) { + _cleanup_(mount_options_free_allp) MountOptions *options = NULL; + _cleanup_free_ char *source_escaped = NULL; + char *source, *tuple; + int permissive; + + r = sd_bus_message_enter_container(message, 'r', "sba(ss)"); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sb", &source, &permissive); + if (r <= 0) + break; + + if (!path_is_absolute(source)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not absolute.", source); + if (!path_is_normalized(source)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path %s is not normalized.", source); + + /* Need to store them in the unit with the escapes, so that they can be parsed again */ + source_escaped = shell_escape(source, ":"); + if (!source_escaped) + return -ENOMEM; + + tuple = strjoin(format_str, + format_str ? " " : "", + permissive ? "-" : "", + source_escaped); + if (!tuple) + return -ENOMEM; + free_and_replace(format_str, tuple); + + r = bus_read_mount_options(message, error, &options, &format_str, ":"); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = mount_image_add(&extension_images, &n_extension_images, + &(MountImage) { + .source = source, + .mount_options = options, + .ignore_enoent = permissive, + .type = MOUNT_IMAGE_EXTENSION, + }); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + if (n_extension_images == 0) { + c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images); + + unit_write_settingf(u, flags, name, "%s=", name); + } else { + for (size_t i = 0; i < n_extension_images; ++i) { + r = mount_image_add(&c->extension_images, &c->n_extension_images, &extension_images[i]); + if (r < 0) + return r; + } + + unit_write_settingf(u, flags|UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS, + name, + "%s=%s", + name, + format_str); + } + } + + extension_images = mount_image_free_many(extension_images, &n_extension_images); + + return 1; } return 0; diff --git a/src/core/execute.c b/src/core/execute.c index d27adbbba5..60d107477b 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -2018,6 +2018,9 @@ bool exec_needs_mount_namespace( if (context->n_mount_images > 0) return true; + if (context->n_extension_images > 0) + return true; + if (!IN_SET(context->mount_flags, 0, MS_SHARED)) return true; @@ -3230,6 +3233,8 @@ static int apply_mount_namespace( 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, + context->extension_images, + context->n_extension_images, propagate_dir, incoming_dir, root_dir || root_image ? params->notify_socket : NULL, @@ -4816,6 +4821,7 @@ void exec_context_done(ExecContext *c) { 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->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images); c->tty_path = mfree(c->tty_path); c->syslog_identifier = mfree(c->syslog_identifier); c->user = mfree(c->user); @@ -5658,6 +5664,19 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { strempty(o->options)); fprintf(f, "\n"); } + + for (size_t i = 0; i < c->n_extension_images; i++) { + MountOptions *o; + + fprintf(f, "%sExtensionImages: %s%s", prefix, + c->extension_images[i].ignore_enoent ? "-": "", + c->extension_images[i].source); + LIST_FOREACH(mount_options, o, c->extension_images[i].mount_options) + fprintf(f, ":%s:%s", + partition_designator_to_string(o->partition_designator), + strempty(o->options)); + fprintf(f, "\n"); + } } bool exec_context_maintains_privileges(const ExecContext *c) { diff --git a/src/core/execute.h b/src/core/execute.h index cf0c8b868b..20e1799b46 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -251,6 +251,8 @@ struct ExecContext { size_t n_temporary_filesystems; MountImage *mount_images; size_t n_mount_images; + MountImage *extension_images; + size_t n_extension_images; uint64_t capability_bounding_set; uint64_t capability_ambient_set; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 6ed6b07db2..6a11ef0d9d 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -28,6 +28,7 @@ $1.RootImageOptions, config_parse_root_image_options, $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.ExtensionImages, config_parse_extension_images, 0, offsetof($1, exec_context) $1.MountImages, config_parse_mount_images, 0, offsetof($1, exec_context) $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 3e7081bf60..c27814ad38 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -5117,6 +5117,148 @@ int config_parse_mount_images( .destination = dresolved, .mount_options = options, .ignore_enoent = permissive, + .type = MOUNT_IMAGE_DISCRETE, + }); + if (r < 0) + return log_oom(); + } +} + +int config_parse_extension_images( + 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) { + + ExecContext *c = data; + const Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->extension_images = mount_image_free_many(c->extension_images, &c->n_extension_images); + return 0; + } + + for (const char *p = rvalue;;) { + _cleanup_free_ char *source = NULL, *tuple = NULL, *sresolved = NULL; + _cleanup_(mount_options_free_allp) MountOptions *options = NULL; + bool permissive = false; + const char *q = NULL; + char *s = NULL; + + r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax %s=%s, ignoring: %m", lvalue, rvalue); + return 0; + } + if (r == 0) + return 0; + + q = tuple; + r = extract_first_word(&q, &source, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, tuple); + return 0; + } + if (r == 0) + continue; + + s = source; + if (s[0] == '-') { + permissive = true; + s++; + } + + r = unit_full_printf(u, s, &sresolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to resolve unit specifiers in \"%s\", ignoring: %m", s); + continue; + } + + r = path_simplify_and_warn(sresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + continue; + + for (;;) { + _cleanup_free_ char *partition = NULL, *mount_options = NULL, *mount_options_resolved = NULL; + MountOptions *o = NULL; + PartitionDesignator partition_designator; + + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", q); + return 0; + } + if (r == 0) + break; + /* Single set of options, applying to the root partition/single filesystem */ + if (r == 1) { + r = unit_full_printf(u, partition, &mount_options_resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in %s, ignoring: %m", partition); + continue; + } + + o = new(MountOptions, 1); + if (!o) + return log_oom(); + *o = (MountOptions) { + .partition_designator = PARTITION_ROOT, + .options = TAKE_PTR(mount_options_resolved), + }; + LIST_APPEND(mount_options, options, o); + + break; + } + + partition_designator = partition_designator_from_string(partition); + if (partition_designator < 0) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Invalid partition name %s, ignoring", partition); + continue; + } + r = unit_full_printf(u, mount_options, &mount_options_resolved); + if (r < 0) { + log_syntax(unit, LOG_WARNING, 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_designator = partition_designator, + .options = TAKE_PTR(mount_options_resolved), + }; + LIST_APPEND(mount_options, options, o); + } + + r = mount_image_add(&c->extension_images, &c->n_extension_images, + &(MountImage) { + .source = sresolved, + .mount_options = options, + .ignore_enoent = permissive, + .type = MOUNT_IMAGE_EXTENSION, }); if (r < 0) return log_oom(); diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index e4a5cb7986..b8a6d5fead 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -138,6 +138,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_timeout_abort); CONFIG_PARSER_PROTOTYPE(config_parse_swap_priority); CONFIG_PARSER_PROTOTYPE(config_parse_mount_images); CONFIG_PARSER_PROTOTYPE(config_parse_socket_timestamping); +CONFIG_PARSER_PROTOTYPE(config_parse_extension_images); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length); diff --git a/src/core/namespace.c b/src/core/namespace.c index 151fc91397..ed07db5c73 100644 --- a/src/core/namespace.c +++ b/src/core/namespace.c @@ -11,6 +11,9 @@ #include "alloc-util.h" #include "base-filesystem.h" #include "dev-setup.h" +#include "env-util.h" +#include "escape.h" +#include "extension-release.h" #include "fd-util.h" #include "format-util.h" #include "fs-util.h" @@ -24,6 +27,7 @@ #include "namespace-util.h" #include "namespace.h" #include "nulstr-util.h" +#include "os-util.h" #include "path-util.h" #include "selinux-util.h" #include "socket-util.h" @@ -41,6 +45,7 @@ typedef enum MountMode { /* This is ordered by priority! */ INACCESSIBLE, + OVERLAY_MOUNT, MOUNT_IMAGES, BIND_MOUNT, BIND_MOUNT_RECURSIVE, @@ -57,6 +62,7 @@ typedef enum MountMode { NOEXEC, EXEC, TMPFS, + EXTENSION_IMAGES, /* Mounted outside the root directory, and used by subsequent mounts */ READWRITE_IMPLICIT, /* Should have the lowest priority. */ _MOUNT_MODE_MAX, } MountMode; @@ -205,6 +211,7 @@ static const MountEntry protect_system_strict_table[] = { static const char * const mount_mode_table[_MOUNT_MODE_MAX] = { [INACCESSIBLE] = "inaccessible", + [OVERLAY_MOUNT] = "overlay", [BIND_MOUNT] = "bind", [BIND_MOUNT_RECURSIVE] = "rbind", [PRIVATE_TMP] = "private-tmp", @@ -392,6 +399,101 @@ static int append_mount_images(MountEntry **p, const MountImage *mount_images, s return 0; } +static int append_extension_images( + MountEntry **p, + const char *root, + const char *extension_dir, + char **hierarchies, + const MountImage *mount_images, + size_t n) { + + _cleanup_strv_free_ char **overlays = NULL; + char **hierarchy; + int r; + + assert(p); + assert(extension_dir); + + if (n == 0) + return 0; + + /* Prepare a list of overlays, that will have as each element a string suitable for being + * passed as a lowerdir= parameter, so start with the hierachy on the root. + * The overlays vector will have the same number of elements and will correspond to the + * hierarchies vector, so they can be iterated upon together. */ + STRV_FOREACH(hierarchy, hierarchies) { + _cleanup_free_ char *prefixed_hierarchy = NULL; + + prefixed_hierarchy = path_join(root, *hierarchy); + if (!prefixed_hierarchy) + return -ENOMEM; + + r = strv_consume(&overlays, TAKE_PTR(prefixed_hierarchy)); + if (r < 0) + return r; + } + + /* First, prepare a mount for each image, but these won't be visible to the unit, instead + * they will be mounted in our propagate directory, and used as a source for the overlay. */ + for (size_t i = 0; i < n; i++) { + _cleanup_free_ char *mount_point = NULL; + const MountImage *m = mount_images + i; + + r = asprintf(&mount_point, "%s/%zu", extension_dir, i); + if (r < 0) + return -ENOMEM; + + for (size_t j = 0; hierarchies && hierarchies[j]; ++j) { + _cleanup_free_ char *prefixed_hierarchy = NULL, *escaped = NULL, *lowerdir = NULL; + + prefixed_hierarchy = path_join(mount_point, hierarchies[j]); + if (!prefixed_hierarchy) + return -ENOMEM; + + escaped = shell_escape(prefixed_hierarchy, ",:"); + if (!escaped) + return -ENOMEM; + + /* Note that lowerdir= parameters are in 'reverse' order, so the + * top-most directory in the overlay comes first in the list. */ + lowerdir = strjoin(escaped, ":", overlays[j]); + if (!lowerdir) + return -ENOMEM; + + free_and_replace(overlays[j], lowerdir); + } + + *((*p)++) = (MountEntry) { + .path_malloc = TAKE_PTR(mount_point), + .image_options = m->mount_options, + .ignore = m->ignore_enoent, + .source_const = m->source, + .mode = EXTENSION_IMAGES, + .has_prefix = true, + }; + } + + /* Then, for each hierarchy, prepare an overlay with the list of lowerdir= strings + * set up earlier. */ + for (size_t i = 0; hierarchies && hierarchies[i]; ++i) { + _cleanup_free_ char *prefixed_hierarchy = NULL; + + prefixed_hierarchy = path_join(root, hierarchies[i]); + if (!prefixed_hierarchy) + return -ENOMEM; + + *((*p)++) = (MountEntry) { + .path_malloc = TAKE_PTR(prefixed_hierarchy), + .options_malloc = TAKE_PTR(overlays[i]), + .mode = OVERLAY_MOUNT, + .has_prefix = true, + .ignore = true, /* If the source image doesn't set the ignore bit it will fail earlier. */ + }; + } + + return 0; +} + static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, size_t n) { assert(p); @@ -494,6 +596,12 @@ static int append_protect_system(MountEntry **p, ProtectSystem protect_system, b static int mount_path_compare(const MountEntry *a, const MountEntry *b) { int d; + /* EXTENSION_IMAGES will be used by other mounts as a base, so sort them first + * regardless of the prefix - they are set up in the propagate directory anyway */ + d = -CMP(a->mode == EXTENSION_IMAGES, b->mode == EXTENSION_IMAGES); + if (d != 0) + return d; + /* If the paths are not equal, then order prefixes first */ d = path_compare(mount_entry_path(a), mount_entry_path(b)); if (d != 0) @@ -640,7 +748,8 @@ static void drop_outside_root(const char *root_directory, MountEntry *m, size_t for (f = m, t = m; f < m + *n; f++) { - if (!path_startswith(mount_entry_path(f), root_directory)) { + /* ExtensionImages bases are opened in /run/systemd/unit-extensions on the host */ + if (f->mode != EXTENSION_IMAGES && !path_startswith(mount_entry_path(f), root_directory)) { log_debug("%s is outside of root directory.", mount_entry_path(f)); mount_entry_done(f); continue; @@ -1003,12 +1112,28 @@ static int mount_run(const MountEntry *m) { return mount_tmpfs(m); } -static int mount_image(const MountEntry *m) { +static int mount_image(const MountEntry *m, const char *root_directory) { + + _cleanup_free_ char *host_os_release_id = NULL, *host_os_release_version_id = NULL, + *host_os_release_sysext_level = NULL; int r; assert(m); - r = verity_dissect_and_mount(mount_entry_source(m), mount_entry_path(m), m->image_options); + if (m->mode == EXTENSION_IMAGES) { + r = parse_os_release( + empty_to_root(root_directory), + "ID", &host_os_release_id, + "VERSION_ID", &host_os_release_version_id, + "SYSEXT_LEVEL", &host_os_release_sysext_level, + NULL); + if (r < 0) + return log_debug_errno(r, "Failed to acquire 'os-release' data of OS tree '%s': %m", empty_to_root(root_directory)); + } + + r = verity_dissect_and_mount( + mount_entry_source(m), mount_entry_path(m), m->image_options, + host_os_release_id, host_os_release_version_id, host_os_release_sysext_level); if (r == -ENOENT && m->ignore) return 0; if (r < 0) @@ -1017,6 +1142,25 @@ static int mount_image(const MountEntry *m) { return 1; } +static int mount_overlay(const MountEntry *m) { + const char *options; + int r; + + assert(m); + + options = strjoina("lowerdir=", mount_entry_options(m)); + + (void) mkdir_p_label(mount_entry_path(m), 0755); + + r = mount_nofollow_verbose(LOG_DEBUG, "overlay", mount_entry_path(m), "overlay", MS_RDONLY, options); + if (r == -ENOENT && m->ignore) + return 0; + if (r < 0) + return r; + + return 1; +} + static int follow_symlink( const char *root_directory, MountEntry *m) { @@ -1173,7 +1317,13 @@ static int apply_one_mount( return mount_run(m); case MOUNT_IMAGES: - return mount_image(m); + return mount_image(m, NULL); + + case EXTENSION_IMAGES: + return mount_image(m, root_directory); + + case OVERLAY_MOUNT: + return mount_overlay(m); default: assert_not_reached("Unknown mode"); @@ -1317,6 +1467,8 @@ static size_t namespace_calculate_mounts( size_t n_bind_mounts, size_t n_temporary_filesystems, size_t n_mount_images, + size_t n_extension_images, + size_t n_hierarchies, const char* tmp_dir, const char* var_tmp_dir, const char *creds_path, @@ -1350,6 +1502,7 @@ static size_t namespace_calculate_mounts( strv_length(empty_directories) + n_bind_mounts + n_mount_images + + (n_extension_images > 0 ? n_hierarchies + n_extension_images : 0) + /* Mount each image plus an overlay per hierarchy */ n_temporary_filesystems + ns_info->private_dev + (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) + @@ -1415,7 +1568,8 @@ static int apply_mounts( if (m->applied) continue; - r = follow_symlink(root, m); + /* ExtensionImages are first opened in the propagate directory, not in the root_directory */ + r = follow_symlink(m->mode != EXTENSION_IMAGES ? root : NULL, m); if (r < 0) { if (error_path && mount_entry_path(m)) *error_path = strdup(mount_entry_path(m)); @@ -1618,6 +1772,8 @@ int setup_namespace( size_t root_hash_sig_size, const char *root_hash_sig_path, const char *verity_data_path, + const MountImage *extension_images, + size_t n_extension_images, const char *propagate_dir, const char *incoming_dir, const char *notify_socket, @@ -1628,9 +1784,10 @@ int setup_namespace( _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; _cleanup_(verity_settings_done) VeritySettings verity = VERITY_SETTINGS_DEFAULT; + _cleanup_strv_free_ char **hierarchies = NULL; MountEntry *m = NULL, *mounts = NULL; bool require_prefix = false, setup_propagate = false; - const char *root; + const char *root, *extension_dir = "/run/systemd/unit-extensions"; size_t n_mounts; int r; @@ -1711,6 +1868,12 @@ int setup_namespace( require_prefix = true; } + if (n_extension_images > 0) { + r = parse_env_extension_hierarchies(&hierarchies); + if (r < 0) + return r; + } + n_mounts = namespace_calculate_mounts( ns_info, read_write_paths, @@ -1722,6 +1885,8 @@ int setup_namespace( n_bind_mounts, n_temporary_filesystems, n_mount_images, + n_extension_images, + strv_length(hierarchies), tmp_dir, var_tmp_dir, creds_path, log_namespace, @@ -1789,6 +1954,10 @@ int setup_namespace( if (r < 0) goto finish; + r = append_extension_images(&m, root, extension_dir, hierarchies, extension_images, n_extension_images); + if (r < 0) + goto finish; + if (ns_info->private_dev) *(m++) = (MountEntry) { .path_const = "/dev", @@ -1948,6 +2117,12 @@ int setup_namespace( if (setup_propagate) (void) mkdir_p(propagate_dir, 0600); + if (n_extension_images > 0) { + /* ExtensionImages mountpoint directories will be created + * while parsing the mounts to create, so have the parent ready */ + (void) mkdir_p(extension_dir, 0600); + } + /* Remount / as SLAVE so that nothing now mounted in the namespace * shows up in the parent */ if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) { @@ -2114,9 +2289,11 @@ int mount_image_add(MountImage **m, size_t *n, const MountImage *item) { if (!s) return -ENOMEM; - d = strdup(item->destination); - if (!d) - return -ENOMEM; + if (item->destination) { + d = strdup(item->destination); + if (!d) + return -ENOMEM; + } LIST_FOREACH(mount_options, i, item->mount_options) { _cleanup_(mount_options_free_allp) MountOptions *o; @@ -2146,6 +2323,7 @@ int mount_image_add(MountImage **m, size_t *n, const MountImage *item) { .destination = TAKE_PTR(d), .mount_options = TAKE_PTR(options), .ignore_enoent = item->ignore_enoent, + .type = item->type, }; return 0; diff --git a/src/core/namespace.h b/src/core/namespace.h index 54d4985f80..cb9d5a5d38 100644 --- a/src/core/namespace.h +++ b/src/core/namespace.h @@ -93,11 +93,19 @@ struct TemporaryFileSystem { char *options; }; +typedef enum MountImageType { + MOUNT_IMAGE_DISCRETE, + MOUNT_IMAGE_EXTENSION, + _MOUNT_IMAGE_TYPE_MAX, + _MOUNT_IMAGE_TYPE_INVALID = -EINVAL, +} MountImageType; + struct MountImage { char *source; - char *destination; + char *destination; /* Unused if MountImageType == MOUNT_IMAGE_EXTENSION */ LIST_HEAD(MountOptions, mount_options); bool ignore_enoent; + MountImageType type; }; int setup_namespace( @@ -129,6 +137,8 @@ int setup_namespace( size_t root_hash_sig_size, const char *root_hash_sig_path, const char *root_verity, + const MountImage *extension_images, + size_t n_extension_images, const char *propagate_dir, const char *incoming_dir, const char *notify_socket, diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index 83130db2fa..eaec48fd5e 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -1766,6 +1766,110 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return 1; } + if (streq(field, "ExtensionImages")) { + 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(sba(ss))"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sba(ss))"); + if (r < 0) + return bus_log_create_error(r); + + for (;;) { + _cleanup_free_ char *source = NULL, *tuple = NULL; + const char *q = NULL, *s = NULL; + bool permissive = false; + + r = extract_first_word(&p, &tuple, NULL, EXTRACT_UNQUOTE|EXTRACT_RETAIN_ESCAPE); + if (r < 0) + return r; + if (r == 0) + break; + + q = tuple; + r = extract_first_word(&q, &source, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + continue; + + s = source; + if (s[0] == '-') { + permissive = true; + s++; + } + + r = sd_bus_message_open_container(m, 'r', "sba(ss)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "sb", s, permissive); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(ss)"); + if (r < 0) + return bus_log_create_error(r); + + for (;;) { + _cleanup_free_ char *partition = NULL, *mount_options = NULL; + + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_UNESCAPE_SEPARATORS, &partition, &mount_options, NULL); + if (r < 0) + return r; + if (r == 0) + break; + /* Single set of options, applying to the root partition/single filesystem */ + if (r == 1) { + r = sd_bus_message_append(m, "(ss)", "root", partition); + if (r < 0) + return bus_log_create_error(r); + + break; + } + + if (partition_designator_from_string(partition) < 0) + return bus_log_create_error(-EINVAL); + + r = sd_bus_message_append(m, "(ss)", partition, 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); + + 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 791d747136..aa4c01bf6c 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -27,6 +27,7 @@ #include "dissect-image.h" #include "dm-util.h" #include "env-file.h" +#include "extension-release.h" #include "fd-util.h" #include "fileio.h" #include "fs-util.h" @@ -2621,7 +2622,14 @@ static const char *const partition_designator_table[] = { [PARTITION_VAR] = "var", }; -int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options) { +int verity_dissect_and_mount( + const char *src, + const char *dest, + const MountOptions *options, + const char *required_host_os_release_id, + const char *required_host_os_release_version_id, + const char *required_host_os_release_sysext_level) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; _cleanup_(dissected_image_unrefp) DissectedImage *dissected_image = NULL; @@ -2683,6 +2691,30 @@ int verity_dissect_and_mount(const char *src, const char *dest, const MountOptio if (r < 0) return log_debug_errno(r, "Failed to mount image: %m"); + /* If we got os-release values from the caller, then we need to match them with the image's + * extension-release.d/ content. Return -EINVAL if there's any mismatch. + * First, check the distro ID. If that matches, then check the new SYSEXT_LEVEL value if + * available, or else fallback to VERSION_ID. */ + if (required_host_os_release_id && + (required_host_os_release_version_id || required_host_os_release_sysext_level)) { + _cleanup_strv_free_ char **extension_release = NULL; + + r = load_extension_release_pairs(dest, dissected_image->image_name, &extension_release); + if (r < 0) + return log_debug_errno(r, "Failed to parse image %s extension-release metadata: %m", dissected_image->image_name); + + r = extension_release_validate( + dissected_image->image_name, + required_host_os_release_id, + required_host_os_release_version_id, + required_host_os_release_sysext_level, + extension_release); + if (r == 0) + return log_debug_errno(SYNTHETIC_ERRNO(ESTALE), "Image %s extension-release metadata does not match the root's", dissected_image->image_name); + if (r < 0) + return log_debug_errno(r, "Failed to compare image %s extension-release metadata with the root's os-release: %m", dissected_image->image_name); + } + if (decrypted_image) { r = decrypted_image_relinquish(decrypted_image); if (r < 0) diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 8907850000..77e7c80c20 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -164,4 +164,4 @@ bool dissected_image_has_verity(const DissectedImage *image, PartitionDesignator int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device, DecryptedImage **ret_decrypted_image); -int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options); +int verity_dissect_and_mount(const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level); diff --git a/src/shared/mount-util.c b/src/shared/mount-util.c index 183a686706..576e4054c2 100644 --- a/src/shared/mount-util.c +++ b/src/shared/mount-util.c @@ -855,7 +855,7 @@ static int mount_in_namespace( mount_tmp_created = true; if (is_image) - r = verity_dissect_and_mount(chased_src, mount_tmp, options); + r = verity_dissect_and_mount(chased_src, mount_tmp, options, NULL, NULL, NULL); else r = mount_follow_verbose(LOG_DEBUG, chased_src, mount_tmp, NULL, MS_BIND, NULL); if (r < 0) diff --git a/src/test/test-namespace.c b/src/test/test-namespace.c index b4db78492e..b162928482 100644 --- a/src/test/test-namespace.c +++ b/src/test/test-namespace.c @@ -175,6 +175,8 @@ static void test_protect_kernel_logs(void) { NULL, NULL, NULL, + 0, + NULL, NULL, NULL, 0, diff --git a/src/test/test-ns.c b/src/test/test-ns.c index 71ccfb88f4..761ee5da86 100644 --- a/src/test/test-ns.c +++ b/src/test/test-ns.c @@ -103,6 +103,8 @@ int main(int argc, char *argv[]) { NULL, NULL, NULL, + 0, + NULL, NULL, NULL, 0, |