From: Luca Boccassi Date: Mon, 20 Oct 2025 23:39:44 +0000 (+0100) Subject: dissect: support mount options when going through mountfsd X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3a8759e5d4b2923af43ea1f0eafb8ae9c2159417;p=thirdparty%2Fsystemd.git dissect: support mount options when going through mountfsd RootImageOptions=/ExtensionImages=/MountImages= all support custom mount options, use the new mountfsd parameters to configure them if they are specified. This requires additioanl privileges via polkit due to security implications of mount options, so document an example policy that allows to use the nosuid mount option. --- 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 00000000000..f81adc64b3f --- /dev/null +++ b/man/system-or-user-ns-mountfsd-mount-options.xml @@ -0,0 +1,32 @@ + + + + + + + + + <para id="plural">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 + <ulink url="https://www.freedesktop.org/software/polkit/docs/latest/">polkit</ulink> policy to allow + specific mount options, for example: + + <example id="example"> + <title>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 be74cb52ef2..27f9f6122dd 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 422c18d2385..172e37d3b0c 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 8883dc6e263..65fb826663e 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 bb90cc1c940..80bbd27fb07 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 03edb6a72d9..14120f2d574 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 16b42c2ed32..55c291c4aba 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 cf2a2284e9f..94f802e780d 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/$(