diff --git a/man/system-or-user-ns-mountfsd-mount-options.xml b/man/system-or-user-ns-mountfsd-mount-options.xml
new file mode 100644
index 0000000000..f81adc64b3
--- /dev/null
+++ b/man/system-or-user-ns-mountfsd-mount-options.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+ When enabled for services running in per-user instances of the service manager
+ using mount options is disabled by default, due to the security implications. It is possible to use a
+ polkit policy to allow
+ specific mount options, for example:
+
+
+ A polkit policy that allows mounting the root partition with nosuid
+
+ /etc/polkit-1/rules.d/mountoptions.rules:
+
+
+polkit.addRule(function(action, subject) {
+ if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &&
+ action.lookup("mount_options") == "root:nosuid") {
+ return polkit.Result.YES;
+ }
+});
+
+
+
+
+
diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml
index be74cb52ef..27f9f6122d 100644
--- a/man/systemd.exec.xml
+++ b/man/systemd.exec.xml
@@ -226,6 +226,9 @@
+
+
+
@@ -524,6 +527,9 @@
+
+
+
@@ -591,6 +597,9 @@
+
+
+
diff --git a/src/core/namespace.c b/src/core/namespace.c
index 422c18d238..172e37d3b0 100644
--- a/src/core/namespace.c
+++ b/src/core/namespace.c
@@ -2619,6 +2619,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
r = mountfsd_mount_image(
p->root_image,
userns_fd,
+ p->root_image_options,
p->root_image_policy,
p->verity,
dissect_image_flags,
diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c
index 8883dc6e26..65fb826663 100644
--- a/src/dissect/dissect.c
+++ b/src/dissect/dissect.c
@@ -2188,6 +2188,7 @@ static int run(int argc, char *argv[]) {
r = mountfsd_mount_image(
arg_image,
userns_fd,
+ /* options= */ NULL,
arg_image_policy,
&arg_verity_settings,
arg_flags,
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index bb90cc1c94..80bbd27fb0 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -6381,6 +6381,7 @@ static int run(int argc, char *argv[]) {
r = mountfsd_mount_image(
arg_image,
userns_fd,
+ /* options= */ NULL,
arg_image_policy,
&arg_verity_settings,
dissect_image_flags,
diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c
index 03edb6a72d..14120f2d57 100644
--- a/src/shared/dissect-image.c
+++ b/src/shared/dissect-image.c
@@ -4934,6 +4934,7 @@ int verity_dissect_and_mount(
r = mountfsd_mount_image(
src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
userns_fd,
+ options,
image_policy,
verity,
dissect_image_flags,
@@ -5100,6 +5101,7 @@ static void mount_image_reply_parameters_done(MountImageReplyParameters *p) {
int mountfsd_mount_image_fd(
int image_fd,
int userns_fd,
+ const MountOptions *options,
const ImagePolicy *image_policy,
const VeritySettings *verity,
DissectImageFlags flags,
@@ -5171,6 +5173,19 @@ int mountfsd_mount_image_fd(
return log_error_errno(r, "Failed to push verity data fd into varlink connection: %m");
}
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *mount_options = NULL;
+ for (PartitionDesignator i = 0; i < _PARTITION_DESIGNATOR_MAX; i++) {
+ const char *o = mount_options_from_designator(options, i);
+ if (!o)
+ continue;
+
+ r = sd_json_variant_merge_objectbo(
+ &mount_options,
+ SD_JSON_BUILD_PAIR_STRING(partition_designator_to_string(i), o));
+ if (r < 0)
+ return log_error_errno(r, "Failed to build mount options array: %m");
+ }
+
sd_json_variant *reply = NULL;
r = varlink_callbo_and_log(
vl,
@@ -5182,6 +5197,7 @@ int mountfsd_mount_image_fd(
SD_JSON_BUILD_PAIR_BOOLEAN("readOnly", FLAGS_SET(flags, DISSECT_IMAGE_MOUNT_READ_ONLY)),
SD_JSON_BUILD_PAIR_BOOLEAN("growFileSystems", FLAGS_SET(flags, DISSECT_IMAGE_GROWFS)),
SD_JSON_BUILD_PAIR_CONDITION(!!ps, "imagePolicy", SD_JSON_BUILD_STRING(ps)),
+ JSON_BUILD_PAIR_VARIANT_NON_NULL("mountOptions", mount_options),
SD_JSON_BUILD_PAIR_BOOLEAN("veritySharing", FLAGS_SET(flags, DISSECT_IMAGE_VERITY_SHARE)),
SD_JSON_BUILD_PAIR_CONDITION(verity_data_fd >= 0, "verityDataFileDescriptor", SD_JSON_BUILD_UNSIGNED(userns_fd >= 0 ? 2 : 1)),
SD_JSON_BUILD_PAIR_CONDITION(verity && iovec_is_set(&verity->root_hash), "verityRootHash", JSON_BUILD_IOVEC_HEX(&verity->root_hash)),
@@ -5274,6 +5290,7 @@ int mountfsd_mount_image_fd(
int mountfsd_mount_image(
const char *path,
int userns_fd,
+ const MountOptions *options,
const ImagePolicy *image_policy,
const VeritySettings *verity,
DissectImageFlags flags,
@@ -5289,7 +5306,7 @@ int mountfsd_mount_image(
return log_error_errno(errno, "Failed to open '%s': %m", path);
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL;
- r = mountfsd_mount_image_fd(image_fd, userns_fd, image_policy, verity, flags, &di);
+ r = mountfsd_mount_image_fd(image_fd, userns_fd, options, image_policy, verity, flags, &di);
if (r < 0)
return r;
diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h
index 16b42c2ed3..55c291c4ab 100644
--- a/src/shared/dissect-image.h
+++ b/src/shared/dissect-image.h
@@ -268,8 +268,8 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m
int get_common_dissect_directory(char **ret);
-int mountfsd_mount_image_fd(int image_fd, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
-int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
+int mountfsd_mount_image_fd(int image_fd, int userns_fd, const MountOptions *options, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
+int mountfsd_mount_image(const char *path, int userns_fd, const MountOptions *options, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret);
int mountfsd_mount_directory_fd(int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
diff --git a/test/units/TEST-50-DISSECT.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh
index cf2a2284e9..94f802e780 100755
--- a/test/units/TEST-50-DISSECT.mountfsd.sh
+++ b/test/units/TEST-50-DISSECT.mountfsd.sh
@@ -91,6 +91,43 @@ if [ "$VERITY_SIG_SUPPORTED" -eq 1 ]; then
--property ExtensionImagePolicy=root=verity+signed+absent:usr=verity+signed+absent \
bash -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(/etc/polkit-1/rules.d/mountoptions.rules <<'EOF'
+polkit.addRule(function(action, subject) {
+ if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &&
+ action.lookup("mount_options") == "root:nosuid") {
+ return polkit.Result.YES;
+ }
+});
+EOF
+ systemctl try-reload-or-restart polkit.service
+
+ systemd-run -M testuser@ --user --pipe --wait \
+ --property RootImage="$MINIMAL_IMAGE.gpt" \
+ --property RootImageOptions="root:nosuid" \
+ sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && mount | grep -F squashfs | grep -q -F nosuid"
+
+ systemd-run -M testuser@ --user --pipe --wait \
+ --property RootImage="$MINIMAL_IMAGE.raw" \
+ --property ExtensionImages=/tmp/app0.raw \
+ --property MountImages=/tmp/app0.raw:/var/tmp:nosuid \
+ sh -c "test -e \"/dev/mapper/${MINIMAL_IMAGE_ROOTHASH}-verity\" && test -e \"/dev/mapper/$(