summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorLuca Boccassi <luca.boccassi@microsoft.com>2020-07-14 16:18:41 +0100
committerLuca Boccassi <luca.boccassi@microsoft.com>2020-08-05 21:34:55 +0100
commitb3d133148ea802e44ec913b2766c811ac2316f9a (patch)
treea4a70c936dd8697cd254aaf5c0be4d0294253ca7 /src/core
parenta082edd53ac9da4a8e06281360754eb15bd1389f (diff)
downloadsystemd-b3d133148ea802e44ec913b2766c811ac2316f9a.tar.gz
core: new feature MountImages
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.
Diffstat (limited to 'src/core')
-rw-r--r--src/core/dbus-execute.c102
-rw-r--r--src/core/execute.c15
-rw-r--r--src/core/execute.h2
-rw-r--r--src/core/load-fragment-gperf.gperf.m41
-rw-r--r--src/core/load-fragment.c88
-rw-r--r--src/core/load-fragment.h1
-rw-r--r--src/core/namespace.c137
-rw-r--r--src/core/namespace.h13
-rw-r--r--src/core/unit.c4
9 files changed, 360 insertions, 3 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 49729799ab..d5a24b9ab7 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -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;
diff --git a/src/core/execute.c b/src/core/execute.c
index 39ffcba580..123396f6f0 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -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) {
diff --git a/src/core/execute.h b/src/core/execute.h
index 349f583c1a..631279038d 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -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;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index a7c9bd9f71..b9e7769e4e 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -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)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 2a2a5af58f..90eb52f432 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -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,
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 253de9467f..2672db5ace 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -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);
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 16d40fedc0..f2288df79b 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -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;
diff --git a/src/core/namespace.h b/src/core/namespace.h
index 258bd7c131..d1e0a28562 100644
--- a/src/core/namespace.h
+++ b/src/core/namespace.h
@@ -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_;
diff --git a/src/core/unit.c b/src/core/unit.c
index 2c09def06f..d6eb4990fe 100644
--- a/src/core/unit.c
+++ b/src/core/unit.c
@@ -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)