]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
dissect: support mount options when going through mountfsd 39394/head
authorLuca Boccassi <luca.boccassi@gmail.com>
Mon, 20 Oct 2025 23:39:44 +0000 (00:39 +0100)
committerLuca Boccassi <luca.boccassi@gmail.com>
Tue, 6 Jan 2026 23:47:53 +0000 (00:47 +0100)
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.

man/system-or-user-ns-mountfsd-mount-options.xml [new file with mode: 0644]
man/systemd.exec.xml
src/core/namespace.c
src/dissect/dissect.c
src/nspawn/nspawn.c
src/shared/dissect-image.c
src/shared/dissect-image.h
test/units/TEST-50-DISSECT.mountfsd.sh
test/units/TEST-50-DISSECT.sh

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 (file)
index 0000000..f81adc6
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE refsect1 PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN" "http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd">
+
+<!--
+  SPDX-License-Identifier: LGPL-2.1-or-later
+-->
+
+<refsect1>
+  <title/>
+
+  <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</title>
+
+      <para><filename index="false">/etc/polkit-1/rules.d/mountoptions.rules</filename>:
+      </para>
+      <programlisting>
+polkit.addRule(function(action, subject) {
+    if (action.id == "io.systemd.mount-file-system.mount-untrusted-image-privately" &amp;&amp;
+            action.lookup("mount_options") == "root:nosuid") {
+        return polkit.Result.YES;
+    }
+});
+</programlisting>
+    </example>
+  </para>
+
+</refsect1>
index be74cb52ef2d9531b882563d01cbffdf9a81295f..27f9f6122dd0ac87b37f418d24951e84fd106f31 100644 (file)
 
         <xi:include href="system-only.xml" xpointer="singular"/>
 
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="plural"/>
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="example"/>
+
         <xi:include href="version-info.xml" xpointer="v247"/></listitem>
       </varlistentry>
 
 
         <xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/>
 
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="plural"/>
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="example"/>
+
         <xi:include href="version-info.xml" xpointer="v247"/></listitem>
       </varlistentry>
 
 
         <xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/>
 
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="plural"/>
+        <xi:include href="system-or-user-ns-mountfsd-mount-options.xml" xpointer="example"/>
+
         <xi:include href="version-info.xml" xpointer="v248"/></listitem>
       </varlistentry>
 
index 422c18d23851b8ebea671a29f226203f3f19707c..172e37d3b0c66d686262b7f06ddd6e16b6610c00 100644 (file)
@@ -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,
index 8883dc6e263362638b10637ee4c0195b0c140c40..65fb826663e91c5de858e5c251ae82fe9388538b 100644 (file)
@@ -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,
index bb90cc1c9404d79e944e8b6fdfccd3878516f01c..80bbd27fb07d5e425ea2eafbf56a01a441d5602a 100644 (file)
@@ -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,
index 03edb6a72d9cdaf8f72450cca9c8a6a72f7a27e9..14120f2d574fbe394b195c981d08eb47ec33747b 100644 (file)
@@ -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;
 
index 16b42c2ed32e705a6980361782c829de7d64a92e..55c291c4abae9a3e7f35262655bdffcd7a064a05 100644 (file)
@@ -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);
 
index cf2a2284e9feedd3d500f1adf4567c77a48cf296..94f802e780d6cb3154fd5e285ed4297f2ad58d53 100755 (executable)
@@ -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/$(</tmp/app0.roothash)-verity\"")
     mv /tmp/app0.roothash.p7s.bak /tmp/app0.roothash.p7s
+
+    # Mount options should not be allowed without elevated privileges
+    (! systemd-run -M testuser@ --user --pipe --wait \
+        --property RootImage="$MINIMAL_IMAGE.gpt" \
+        --property RootImageOptions="root:ro,noatime,nosuid home:ro,dev nosuid,dev" \
+        --property RootImageOptions="home:ro,dev nosuid,dev,%%foo" \
+        true)
+    (! systemd-run -M testuser@ --user --pipe --wait \
+        --property RootImage="$MINIMAL_IMAGE.raw" \
+        --property ExtensionImages=/tmp/app0.raw \
+        --property MountImages=/tmp/app0.raw:/var/tmp:noatime,nosuid \
+        true)
+
+    mkdir -p /etc/polkit-1/rules.d
+    cat >/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/$(</tmp/app0.roothash)-verity\" && mount | grep -F /var/tmp | grep -q -F nosuid"
+
+    rm -f /etc/polkit-1/rules.d/mountoptions.rules
+    systemctl try-reload-or-restart polkit.service
 fi
 
 # Bare squashfs without any verity or signature also should be rejected, even if we ask to trust it
index 40b502529782d398d0a0d6a5d8291620eaecba8d..e0a17e58db70d6b10b3d3e75de5e781079423e0f 100755 (executable)
@@ -25,6 +25,7 @@ at_exit() {
     done < <(find "${IMAGE_DIR}" -mindepth 1 -maxdepth 1 -type d)
 
     rm -rf "$IMAGE_DIR"
+    rm -rf /etc/polkit-1/rules.d/mountoptions.rules
 
     rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules