]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
core: new feature MountImages 16321/head
authorLuca Boccassi <luca.boccassi@microsoft.com>
Tue, 14 Jul 2020 15:18:41 +0000 (16:18 +0100)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Wed, 5 Aug 2020 20:34:55 +0000 (21:34 +0100)
Follows the same pattern and features as RootImage, but allows an
arbitrary mount point under / to be specified by the user, and
multiple values - like BindPaths.

Original implementation by @topimiettinen at:
https://github.com/systemd/systemd/pull/14451
Reworked to use dissect's logic instead of bare libmount() calls
and other review comments.
Thanks Topi for the initial work to come up with and implement
this useful feature.

15 files changed:
man/systemd.exec.xml
src/core/dbus-execute.c
src/core/execute.c
src/core/execute.h
src/core/load-fragment-gperf.gperf.m4
src/core/load-fragment.c
src/core/load-fragment.h
src/core/namespace.c
src/core/namespace.h
src/core/unit.c
src/shared/bus-unit-util.c
src/systemctl/systemctl.c
src/test/test-namespace.c
src/test/test-ns.c
test/units/testsuite-50.sh

index c5d755e8976471c959537fc956fb624de142e062..87019dae4f5599fb979b3521b62f445950f841e2 100644 (file)
         <xi:include href="system-only.xml" xpointer="singular"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>MountImages=</varname></term>
+
+        <listitem><para>This setting is similar to <varname>RootImage=</varname> in that it mounts a file
+        system hierarchy from a block device node or loopback file, but the destination directory can be
+        specified as well as mount options. This option expects a whitespace separated list of mount
+        definitions. Each definition consists of a colon-separated tuple of source path and destination
+        directory. Each mount definition may be prefixed with <literal>-</literal>, in which case it will be
+        ignored when its source path does not exist. The source argument is a path to a block device node or
+        regular file. If source or destination contain a <literal>:</literal>, it needs to be escaped as
+        <literal>\:</literal>.
+        The device node or file system image file needs to follow the same rules as specified
+        for <varname>RootImage=</varname>. Any mounts created with this option are specific to the unit, and
+        are not visible in the host's mount table.</para>
+
+        <para>These settings may be used more than once, each usage appends to the unit's list of mount
+        paths. If the empty string is assigned, the entire list of mount paths defined prior to this is
+        reset.</para>
+
+        <para>Note that the destination directory must exist or systemd must be able to create it.  Thus, it
+        is not possible to use those options for mount points nested underneath paths specified in
+        <varname>InaccessiblePaths=</varname>, or under <filename>/home/</filename> and other protected
+        directories if <varname>ProtectHome=yes</varname> is specified.</para>
+
+        <para>When <varname>DevicePolicy=</varname> is set to <literal>closed</literal> or
+        <literal>strict</literal>, or set to <literal>auto</literal> and <varname>DeviceAllow=</varname> is
+        set, then this setting adds <filename>/dev/loop-control</filename> with <constant>rw</constant> mode,
+        <literal>block-loop</literal> and <literal>block-blkext</literal> with <constant>rwm</constant> mode
+        to <varname>DeviceAllow=</varname>. See
+        <citerefentry><refentrytitle>systemd.resource-control</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+        for the details about <varname>DevicePolicy=</varname> or <varname>DeviceAllow=</varname>. Also, see
+        <varname>PrivateDevices=</varname> below, as it may change the setting of
+        <varname>DevicePolicy=</varname>.</para>
+
+        <xi:include href="system-only.xml" xpointer="singular"/></listitem>
+      </varlistentry>
     </variablelist>
   </refsect1>
 
index 49729799abfd9c6418efa2bf3a342d3ab99b66f2..d5a24b9ab731f145e76d36b09fa6b00c077fdb25 100644 (file)
@@ -815,6 +815,40 @@ static int property_get_root_image_options(
         return sd_bus_message_close_container(reply);
 }
 
+static int property_get_mount_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', "(ssb)");
+        if (r < 0)
+                return r;
+
+        for (size_t i = 0; i < c->n_mount_images; i++) {
+                r = sd_bus_message_append(
+                                reply, "(ssb)",
+                                c->mount_images[i].source,
+                                c->mount_images[i].destination,
+                                c->mount_images[i].ignore_enoent);
+                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),
@@ -863,6 +897,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("MountImages", "a(ssb)", 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),
         SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST),
@@ -2896,6 +2931,73 @@ int bus_exec_context_set_transient_property(
                         return 1;
                 }
 
+        } else if (streq(name, "MountImages")) {
+                _cleanup_free_ char *format_str = NULL;
+                MountImage *mount_images = NULL;
+                size_t n_mount_images = 0;
+                char *source, *destination;
+                int permissive;
+
+                r = sd_bus_message_enter_container(message, 'a', "(ssb)");
+                if (r < 0)
+                        return r;
+
+                while ((r = sd_bus_message_read(message, "(ssb)", &source, &destination, &permissive)) > 0) {
+                        char *tuple;
+
+                        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);
+                        if (!path_is_absolute(destination))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not absolute.", destination);
+                        if (!path_is_normalized(destination))
+                                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path %s is not normalized.", destination);
+
+                        tuple = strjoin(format_str, format_str ? " " : "", permissive ? "-" : "", source, ":", destination);
+                        if (!tuple)
+                                return -ENOMEM;
+                        free_and_replace(format_str, tuple);
+
+                        r = mount_image_add(&mount_images, &n_mount_images,
+                                            &(MountImage) {
+                                                    .source = source,
+                                                    .destination = destination,
+                                                    .ignore_enoent = permissive,
+                                            });
+                        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_mount_images == 0) {
+                                c->mount_images = mount_image_free_many(c->mount_images, &c->n_mount_images);
+
+                                unit_write_settingf(u, flags, name, "%s=", name);
+                        } else {
+                                for (size_t i = 0; i < n_mount_images; ++i) {
+                                        r = mount_image_add(&c->mount_images, &c->n_mount_images, &mount_images[i]);
+                                        if (r < 0)
+                                                return r;
+                                }
+
+                                unit_write_settingf(u, flags|UNIT_ESCAPE_C|UNIT_ESCAPE_SPECIFIERS,
+                                                    name,
+                                                    "%s=%s",
+                                                    name,
+                                                    format_str);
+                        }
+                }
+
+                mount_images = mount_image_free_many(mount_images, &n_mount_images);
+
+                return 1;
         }
 
         return 0;
index 39ffcba5802dff4b4e78c4b216fcef9239da1514..123396f6f0b6c7bdef71e033c521f76ed4c6f44d 100644 (file)
@@ -1932,6 +1932,9 @@ static bool exec_needs_mount_namespace(
         if (context->n_temporary_filesystems > 0)
                 return true;
 
+        if (context->n_mount_images > 0)
+                return true;
+
         if (!IN_SET(context->mount_flags, 0, MS_SHARED))
                 return true;
 
@@ -2570,6 +2573,9 @@ static bool insist_on_sandboxing(
         if (root_dir || root_image)
                 return true;
 
+        if (context->n_mount_images > 0)
+                return true;
+
         if (context->dynamic_user)
                 return true;
 
@@ -2669,6 +2675,8 @@ static int apply_mount_namespace(
                             n_bind_mounts,
                             context->temporary_filesystems,
                             context->n_temporary_filesystems,
+                            context->mount_images,
+                            context->n_mount_images,
                             tmp_dir,
                             var_tmp_dir,
                             context->log_namespace,
@@ -4234,6 +4242,7 @@ void exec_context_done(ExecContext *c) {
         temporary_filesystem_free_many(c->temporary_filesystems, c->n_temporary_filesystems);
         c->temporary_filesystems = NULL;
         c->n_temporary_filesystems = 0;
+        c->mount_images = mount_image_free_many(c->mount_images, &c->n_mount_images);
 
         cpu_set_reset(&c->cpu_set);
         numa_policy_reset(&c->numa_policy);
@@ -5025,6 +5034,12 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) {
                 else
                         fprintf(f, "%d\n", c->syscall_errno);
         }
+
+        for (i = 0; i < c->n_mount_images; i++)
+                fprintf(f, "%sMountImages: %s%s:%s\n", prefix,
+                        c->mount_images[i].ignore_enoent ? "-": "",
+                        c->mount_images[i].source,
+                        c->mount_images[i].destination);
 }
 
 bool exec_context_maintains_privileges(const ExecContext *c) {
index 349f583c1a6697330e83144f8652b57da4d81c8b..631279038d324584d31e26e380da65075f9a6403 100644 (file)
@@ -239,6 +239,8 @@ struct ExecContext {
         size_t n_bind_mounts;
         TemporaryFileSystem *temporary_filesystems;
         size_t n_temporary_filesystems;
+        MountImage *mount_images;
+        size_t n_mount_images;
 
         uint64_t capability_bounding_set;
         uint64_t capability_ambient_set;
index a7c9bd9f711ec6bfb255bad69c0431a6e4aadb08..b9e7769e4e38b83cb522972e0bc25232aedf7db5 100644 (file)
@@ -27,6 +27,7 @@ $1.RootImageOptions,             config_parse_root_image_options,    0,
 $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.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)
 $1.SupplementaryGroups,          config_parse_user_group_strv_compat, 0,                            offsetof($1, exec_context.supplementary_groups)
index 2a2a5af58fd8de5dcd1e17b26b4fbe74bf05caae..90eb52f432d8bdfee0079a57bc1923f9f9551563 100644 (file)
@@ -4675,6 +4675,94 @@ int config_parse_bind_paths(
         return 0;
 }
 
+int config_parse_mount_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) {
+
+        _cleanup_strv_free_ char **l = NULL;
+        ExecContext *c = data;
+        const Unit *u = userdata;
+        char **source = NULL, **destination = NULL;
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+        assert(data);
+
+        if (isempty(rvalue)) {
+                /* Empty assignment resets the list */
+                c->mount_images = mount_image_free_many(c->mount_images, &c->n_mount_images);
+                return 0;
+        }
+
+        r = strv_split_colon_pairs(&l, rvalue);
+        if (r == -ENOMEM)
+                return log_oom();
+        if (r < 0) {
+                log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s, ignoring: %s", lvalue, rvalue);
+                return 0;
+        }
+
+        STRV_FOREACH_PAIR(source, destination, l) {
+                _cleanup_free_ char *sresolved = NULL, *dresolved = NULL;
+                char *s = NULL;
+                bool permissive = false;
+
+                r = unit_full_printf(u, *source, &sresolved);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                   "Failed to resolve unit specifiers in \"%s\", ignoring: %m", *source);
+                        continue;
+                }
+
+                s = sresolved;
+                if (s[0] == '-') {
+                        permissive = true;
+                        s++;
+                }
+
+                r = path_simplify_and_warn(s, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+                if (r < 0)
+                        continue;
+
+                if (isempty(*destination)) {
+                        log_syntax(unit, LOG_ERR, filename, line, 0, "Missing destination in %s, ignoring: %s", lvalue, rvalue);
+                        continue;
+                }
+
+                r = unit_full_printf(u, *destination, &dresolved);
+                if (r < 0) {
+                        log_syntax(unit, LOG_ERR, filename, line, r,
+                                        "Failed to resolve specifiers in \"%s\", ignoring: %m", *destination);
+                        continue;
+                }
+
+                r = path_simplify_and_warn(dresolved, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+                if (r < 0)
+                        continue;
+
+                r = mount_image_add(&c->mount_images, &c->n_mount_images,
+                                    &(MountImage) {
+                                            .source = s,
+                                            .destination = dresolved,
+                                            .ignore_enoent = permissive,
+                                    });
+                if (r < 0)
+                        return log_oom();
+        }
+
+        return 0;
+}
+
 int config_parse_job_timeout_sec(
                 const char* unit,
                 const char *filename,
index 253de9467f09d10bade6b0e28c24529745ede8b2..2672db5ace277f090421dea13693a2d6466d69af 100644 (file)
@@ -128,6 +128,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_output_restricted);
 CONFIG_PARSER_PROTOTYPE(config_parse_crash_chvt);
 CONFIG_PARSER_PROTOTYPE(config_parse_timeout_abort);
 CONFIG_PARSER_PROTOTYPE(config_parse_swap_priority);
+CONFIG_PARSER_PROTOTYPE(config_parse_mount_images);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 16d40fedc0c0e5fa54f183984799db93e066d851..f2288df79b42c785e56577e9a39c306c81a9330b 100644 (file)
@@ -15,6 +15,7 @@
 #include "format-util.h"
 #include "fs-util.h"
 #include "label.h"
+#include "list.h"
 #include "loop-util.h"
 #include "loopback-setup.h"
 #include "mkdir.h"
@@ -40,6 +41,7 @@
 typedef enum MountMode {
         /* This is ordered by priority! */
         INACCESSIBLE,
+        MOUNT_IMAGES,
         BIND_MOUNT,
         BIND_MOUNT_RECURSIVE,
         PRIVATE_TMP,
@@ -65,12 +67,13 @@ typedef struct MountEntry {
         bool nosuid:1;            /* Shall set MS_NOSUID on the mount itself */
         bool applied:1;           /* Already applied */
         char *path_malloc;        /* Use this instead of 'path_const' if we had to allocate memory */
-        const char *source_const; /* The source path, for bind mounts */
+        const char *source_const; /* The source path, for bind mounts or images */
         char *source_malloc;
         const char *options_const;/* Mount options for tmpfs */
         char *options_malloc;
         unsigned long flags;      /* Mount flags used by EMPTY_DIR and TMPFS. Do not include MS_RDONLY here, but please use read_only. */
         unsigned n_followed;
+        LIST_FIELDS(MountEntry, mount_entry);
 } MountEntry;
 
 /* If MountAPIVFS= is used, let's mount /sys and /proc into the it, but only as a fallback if the user hasn't mounted
@@ -205,6 +208,7 @@ static const char * const mount_mode_table[_MOUNT_MODE_MAX] = {
         [READONLY]             = "read-only",
         [READWRITE]            = "read-write",
         [TMPFS]                = "tmpfs",
+        [MOUNT_IMAGES]         = "mount-images",
         [READWRITE_IMPLICIT]   = "rw-implicit",
 };
 
@@ -325,6 +329,23 @@ static int append_bind_mounts(MountEntry **p, const BindMount *binds, size_t n)
         return 0;
 }
 
+static int append_mount_images(MountEntry **p, const MountImage *mount_images, size_t n) {
+        assert(p);
+
+        for (size_t i = 0; i < n; i++) {
+                const MountImage *m = mount_images + i;
+
+                *((*p)++) = (MountEntry) {
+                        .path_const = m->destination,
+                        .mode = MOUNT_IMAGES,
+                        .source_const = m->source,
+                        .ignore = m->ignore_enoent,
+                };
+        }
+
+        return 0;
+}
+
 static int append_tmpfs_mounts(MountEntry **p, const TemporaryFileSystem *tmpfs, size_t n) {
         assert(p);
 
@@ -882,6 +903,61 @@ static int mount_tmpfs(const MountEntry *m) {
         return 1;
 }
 
+static int mount_images(const MountEntry *m) {
+        _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+        _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, *hash_sig = NULL;
+        DissectImageFlags dissect_image_flags = m->read_only ? DISSECT_IMAGE_READ_ONLY : 0;
+        size_t root_hash_size = 0;
+        int r;
+
+        r = verity_metadata_load(mount_entry_source(m), NULL, &root_hash_decoded, &root_hash_size, &verity_data, &hash_sig);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to load root hash: %m");
+        dissect_image_flags |= verity_data ? DISSECT_IMAGE_NO_PARTITION_TABLE : 0;
+
+        r = loop_device_make_by_path(mount_entry_source(m),
+                                     m->read_only ? O_RDONLY : -1 /* < 0 means writable if possible, read-only as fallback */,
+                                     verity_data ? 0 : LO_FLAGS_PARTSCAN,
+                                     &loop_device);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to create loop device for image: %m");
+
+        r = dissect_image(loop_device->fd, root_hash_decoded, root_hash_size, verity_data, NULL, dissect_image_flags, &dissected_image);
+        /* No partition table? Might be a single-filesystem image, try again */
+        if (!verity_data && r < 0 && r == -ENOPKG)
+                 r = dissect_image(loop_device->fd, root_hash_decoded, root_hash_size, verity_data, NULL, dissect_image_flags|DISSECT_IMAGE_NO_PARTITION_TABLE, &dissected_image);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to dissect image: %m");
+
+        r = dissected_image_decrypt(dissected_image, NULL, root_hash_decoded, root_hash_size, verity_data, hash_sig, NULL, 0, dissect_image_flags, &decrypted_image);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to decrypt dissected image: %m");
+
+        r = mkdir_p_label(mount_entry_path(m), 0755);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to create destination directory %s: %m", mount_entry_path(m));
+        r = umount_recursive(mount_entry_path(m), 0);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to umount under destination directory %s: %m", mount_entry_path(m));
+
+        r = dissected_image_mount(dissected_image, mount_entry_path(m), UID_INVALID, dissect_image_flags);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to mount image: %m");
+
+        if (decrypted_image) {
+                r = decrypted_image_relinquish(decrypted_image);
+                if (r < 0)
+                        return log_debug_errno(r, "Failed to relinquish decrypted image: %m");
+        }
+
+        loop_device_relinquish(loop_device);
+
+        return 1;
+}
+
 static int follow_symlink(
                 const char *root_directory,
                 MountEntry *m) {
@@ -1031,6 +1107,9 @@ static int apply_mount(
         case PROCFS:
                 return mount_procfs(m);
 
+        case MOUNT_IMAGES:
+                return mount_images(m);
+
         default:
                 assert_not_reached("Unknown mode");
         }
@@ -1149,6 +1228,7 @@ static size_t namespace_calculate_mounts(
                 char** empty_directories,
                 size_t n_bind_mounts,
                 size_t n_temporary_filesystems,
+                size_t n_mount_images,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 const char* log_namespace,
@@ -1178,6 +1258,7 @@ static size_t namespace_calculate_mounts(
                 strv_length(inaccessible_paths) +
                 strv_length(empty_directories) +
                 n_bind_mounts +
+                n_mount_images +
                 n_temporary_filesystems +
                 ns_info->private_dev +
                 (ns_info->protect_kernel_tunables ? ELEMENTSOF(protect_kernel_tunables_table) : 0) +
@@ -1267,6 +1348,8 @@ int setup_namespace(
                 size_t n_bind_mounts,
                 const TemporaryFileSystem *temporary_filesystems,
                 size_t n_temporary_filesystems,
+                const MountImage *mount_images,
+                size_t n_mount_images,
                 const char* tmp_dir,
                 const char* var_tmp_dir,
                 const char *log_namespace,
@@ -1374,6 +1457,7 @@ int setup_namespace(
                         empty_directories,
                         n_bind_mounts,
                         n_temporary_filesystems,
+                        n_mount_images,
                         tmp_dir, var_tmp_dir,
                         log_namespace,
                         protect_home, protect_system);
@@ -1427,6 +1511,10 @@ int setup_namespace(
                         };
                 }
 
+                r = append_mount_images(&m, mount_images, n_mount_images);
+                if (r < 0)
+                        goto finish;
+
                 if (ns_info->private_dev) {
                         *(m++) = (MountEntry) {
                                 .path_const = "/dev",
@@ -1741,6 +1829,53 @@ int bind_mount_add(BindMount **b, size_t *n, const BindMount *item) {
         return 0;
 }
 
+MountImage* mount_image_free_many(MountImage *m, size_t *n) {
+        size_t i;
+
+        assert(n);
+        assert(m || *n == 0);
+
+        for (i = 0; i < *n; i++) {
+                free(m[i].source);
+                free(m[i].destination);
+        }
+
+        free(m);
+        *n = 0;
+        return NULL;
+}
+
+int mount_image_add(MountImage **m, size_t *n, const MountImage *item) {
+        _cleanup_free_ char *s = NULL, *d = NULL;
+        MountImage *c;
+
+        assert(m);
+        assert(n);
+        assert(item);
+
+        s = strdup(item->source);
+        if (!s)
+                return -ENOMEM;
+
+        d = strdup(item->destination);
+        if (!d)
+                return -ENOMEM;
+
+        c = reallocarray(*m, *n + 1, sizeof(MountImage));
+        if (!c)
+                return -ENOMEM;
+
+        *m = c;
+
+        c[(*n) ++] = (MountImage) {
+                .source = TAKE_PTR(s),
+                .destination = TAKE_PTR(d),
+                .ignore_enoent = item->ignore_enoent,
+        };
+
+        return 0;
+}
+
 void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n) {
         size_t i;
 
index 258bd7c131443865ae236ac3e24e3e8c230b932b..d1e0a285622e8e24b5bd1dfdb4eebfe73ea6c9de 100644 (file)
@@ -8,6 +8,8 @@
 typedef struct NamespaceInfo NamespaceInfo;
 typedef struct BindMount BindMount;
 typedef struct TemporaryFileSystem TemporaryFileSystem;
+typedef struct MountImage MountImage;
+typedef struct MountEntry MountEntry;
 
 #include <stdbool.h>
 
@@ -72,6 +74,12 @@ struct TemporaryFileSystem {
         char *options;
 };
 
+struct MountImage {
+        char *source;
+        char *destination;
+        bool ignore_enoent;
+};
+
 int setup_namespace(
                 const char *root_directory,
                 const char *root_image,
@@ -85,6 +93,8 @@ int setup_namespace(
                 size_t n_bind_mounts,
                 const TemporaryFileSystem *temporary_filesystems,
                 size_t n_temporary_filesystems,
+                const MountImage *mount_images,
+                size_t n_mount_images,
                 const char *tmp_dir,
                 const char *var_tmp_dir,
                 const char *log_namespace,
@@ -132,6 +142,9 @@ void temporary_filesystem_free_many(TemporaryFileSystem *t, size_t n);
 int temporary_filesystem_add(TemporaryFileSystem **t, size_t *n,
                              const char *path, const char *options);
 
+MountImage* mount_image_free_many(MountImage *m, size_t *n);
+int mount_image_add(MountImage **m, size_t *n, const MountImage *item);
+
 const char* namespace_type_to_string(NamespaceType t) _const_;
 NamespaceType namespace_type_from_string(const char *s) _pure_;
 
index 2c09def06f3b228450cea414b582287422175fe5..d6eb4990fec1d95abd08eb854b3ce41ae11df4d9 100644 (file)
@@ -4527,11 +4527,11 @@ int unit_patch_contexts(Unit *u) {
                     cc->device_policy == CGROUP_DEVICE_POLICY_AUTO)
                         cc->device_policy = CGROUP_DEVICE_POLICY_CLOSED;
 
-                if (ec->root_image &&
+                if ((ec->root_image || !LIST_IS_EMPTY(ec->mount_images)) &&
                     (cc->device_policy != CGROUP_DEVICE_POLICY_AUTO || cc->device_allow)) {
                         const char *p;
 
-                        /* When RootImage= is specified, the following devices are touched. */
+                        /* When RootImage= or MountImages= is specified, the following devices are touched. */
                         FOREACH_STRING(p, "/dev/loop-control", "/dev/mapper/control") {
                                 r = cgroup_add_device_allow(cc, p, "rw");
                                 if (r < 0)
index 30a872342ff732847d1310690eedbda887c3c633..3f4ab65af875e1224851d08330d8f5480b304fcd 100644 (file)
@@ -18,6 +18,7 @@
 #include "hostname-util.h"
 #include "in-addr-util.h"
 #include "ip-protocol-list.h"
+#include "libmount-util.h"
 #include "locale-util.h"
 #include "log.h"
 #include "missing_fs.h"
@@ -1522,6 +1523,65 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                 return 1;
         }
 
+        if (streq(field, "MountImages")) {
+                _cleanup_strv_free_ char **l = NULL;
+                char **source = NULL, **destination = NULL;
+                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(ssb)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = sd_bus_message_open_container(m, 'a', "(ssb)");
+                if (r < 0)
+                        return bus_log_create_error(r);
+
+                r = strv_split_colon_pairs(&l, p);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to parse argument: %m");
+
+                STRV_FOREACH_PAIR(source, destination, l) {
+                        char *s = *source;
+                        bool permissive = false;
+
+                        if (s[0] == '-') {
+                                permissive = true;
+                                s++;
+                        }
+
+                        if (isempty(*destination))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                                        "Missing argument after ':': %s",
+                                                        eq);
+
+                        r = sd_bus_message_append(m, "(ssb)", s, *destination, permissive);
+                        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;
 }
 
index c58a19a099d0780a099c93cf044854842cc75753..d55a89efabcfcd7684815d3cf1967248a6c9d8d3 100644 (file)
@@ -5408,6 +5408,39 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m
                         bus_print_property_value(name, expected_value, value, affinity);
 
                         return 1;
+                } else if (streq(name, "MountImages")) {
+                        _cleanup_free_ char *paths = NULL;
+                        const char *source, *dest;
+                        int ignore_enoent;
+
+                        r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ssb)");
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        while ((r = sd_bus_message_read(m, "(ssb)", &source, &dest, &ignore_enoent)) > 0) {
+                                _cleanup_free_ char *str = NULL;
+
+                                if (isempty(source))
+                                        continue;
+
+                                if (asprintf(&str, "%s%s:%s", ignore_enoent ? "-" : "", source, dest) < 0)
+                                        return log_oom();
+
+                                if (!strextend_with_separator(&paths, " ", str, NULL))
+                                        return log_oom();
+                        }
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        r = sd_bus_message_exit_container(m);
+                        if (r < 0)
+                                return bus_log_parse_error(r);
+
+                        if (all || !isempty(paths))
+                                bus_print_property_value(name, expected_value, value, strempty(paths));
+
+                        return 1;
+
                 }
 
                 break;
index 95021ee7bf7260c597f401f6d24f9e61fd05c9a1..f70b7e778ed01be72911b4eecaddd86aa4b50273 100644 (file)
@@ -159,6 +159,7 @@ static void test_protect_kernel_logs(void) {
                                     NULL,
                                     NULL, 0,
                                     NULL, 0,
+                                    NULL, 0,
                                     NULL,
                                     NULL,
                                     NULL,
index ced287dd6e06d83a1b63d869ac8690ac35f9571c..cba8ee2b2b0ddc9054d5c4003429e0906145cd78 100644 (file)
@@ -71,6 +71,8 @@ int main(int argc, char *argv[]) {
                             NULL,
                             &(BindMount) { .source = (char*) "/usr/bin", .destination = (char*) "/etc/systemd", .read_only = true }, 1,
                             &(TemporaryFileSystem) { .path = (char*) "/var", .options = (char*) "ro" }, 1,
+                            NULL,
+                            0,
                             tmp_dir,
                             var_tmp_dir,
                             NULL,
index 28144b378fdd73b40d0b6b3ef04755e6d355b181..587184e8548678eb1a0da0a1fc158537dea93104 100755 (executable)
@@ -155,6 +155,26 @@ journalctl -b -u testservice-50b.service | grep -F "squashfs" | grep -q -F "noat
 # Check that specifier escape is applied %%foo -> %foo
 busctl get-property org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/testservice_2d50b_2eservice org.freedesktop.systemd1.Service RootImageOptions | grep -F "nosuid,dev,%foo"
 
+# Now do some checks with MountImages, both by itself and in combination with RootImage, and as single FS or GPT image
+systemd-run -t --property MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" /usr/bin/cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -t --property MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" /usr/bin/cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -t --property MountImages="${image}.raw:/run/img2\:3" /usr/bin/cat /run/img2:3/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -t --property TemporaryFileSystem=/run --property RootImage=${image}.raw --property MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" /usr/bin/cat /usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -t --property TemporaryFileSystem=/run --property RootImage=${image}.raw --property MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" /usr/bin/cat /run/img1/usr/lib/os-release | grep -q -F "MARKER=1"
+systemd-run -t --property TemporaryFileSystem=/run --property RootImage=${image}.gpt --property RootHash=${roothash} --property MountImages="${image}.gpt:/run/img1 ${image}.raw:/run/img2" /usr/bin/cat /run/img2/usr/lib/os-release | grep -q -F "MARKER=1"
+cat >/run/systemd/system/testservice-50.service <<EOF
+[Service]
+TemporaryFileSystem=/run
+RootImage=${image}.raw
+MountImages=${image}.gpt:/run/img1
+MountImages=${image}.raw:/run/img2\:3
+ExecStart=/usr/bin/cat /run/img1/usr/lib/os-release
+ExecStart=/usr/bin/cat /run/img2:3/usr/lib/os-release
+Type=oneshot
+EOF
+systemctl start testservice-50.service
+journalctl -b -u testservice-50.service | grep -q -F "MARKER=1"
+
 echo OK > /testok
 
 exit 0