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.
This commit is contained in:
Luca Boccassi
2026-01-07 00:47:53 +01:00
parent 78b40aea61
commit 3a8759e5d4
9 changed files with 102 additions and 3 deletions
@@ -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>
+9
View File
@@ -226,6 +226,9 @@
<xi:include href="system-only.xml" xpointer="singular"/> <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> <xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry> </varlistentry>
@@ -524,6 +527,9 @@
<xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/> <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> <xi:include href="version-info.xml" xpointer="v247"/></listitem>
</varlistentry> </varlistentry>
@@ -591,6 +597,9 @@
<xi:include href="system-or-user-ns-mountfsd.xml" xpointer="singular"/> <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> <xi:include href="version-info.xml" xpointer="v248"/></listitem>
</varlistentry> </varlistentry>
+1
View File
@@ -2619,6 +2619,7 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
r = mountfsd_mount_image( r = mountfsd_mount_image(
p->root_image, p->root_image,
userns_fd, userns_fd,
p->root_image_options,
p->root_image_policy, p->root_image_policy,
p->verity, p->verity,
dissect_image_flags, dissect_image_flags,
+1
View File
@@ -2188,6 +2188,7 @@ static int run(int argc, char *argv[]) {
r = mountfsd_mount_image( r = mountfsd_mount_image(
arg_image, arg_image,
userns_fd, userns_fd,
/* options= */ NULL,
arg_image_policy, arg_image_policy,
&arg_verity_settings, &arg_verity_settings,
arg_flags, arg_flags,
+1
View File
@@ -6381,6 +6381,7 @@ static int run(int argc, char *argv[]) {
r = mountfsd_mount_image( r = mountfsd_mount_image(
arg_image, arg_image,
userns_fd, userns_fd,
/* options= */ NULL,
arg_image_policy, arg_image_policy,
&arg_verity_settings, &arg_verity_settings,
dissect_image_flags, dissect_image_flags,
+18 -1
View File
@@ -4934,6 +4934,7 @@ int verity_dissect_and_mount(
r = mountfsd_mount_image( r = mountfsd_mount_image(
src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src, src_fd >= 0 ? FORMAT_PROC_FD_PATH(src_fd) : src,
userns_fd, userns_fd,
options,
image_policy, image_policy,
verity, verity,
dissect_image_flags, dissect_image_flags,
@@ -5100,6 +5101,7 @@ static void mount_image_reply_parameters_done(MountImageReplyParameters *p) {
int mountfsd_mount_image_fd( int mountfsd_mount_image_fd(
int image_fd, int image_fd,
int userns_fd, int userns_fd,
const MountOptions *options,
const ImagePolicy *image_policy, const ImagePolicy *image_policy,
const VeritySettings *verity, const VeritySettings *verity,
DissectImageFlags flags, 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"); 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; sd_json_variant *reply = NULL;
r = varlink_callbo_and_log( r = varlink_callbo_and_log(
vl, 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("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_BOOLEAN("growFileSystems", FLAGS_SET(flags, DISSECT_IMAGE_GROWFS)),
SD_JSON_BUILD_PAIR_CONDITION(!!ps, "imagePolicy", SD_JSON_BUILD_STRING(ps)), 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_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_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)), 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( int mountfsd_mount_image(
const char *path, const char *path,
int userns_fd, int userns_fd,
const MountOptions *options,
const ImagePolicy *image_policy, const ImagePolicy *image_policy,
const VeritySettings *verity, const VeritySettings *verity,
DissectImageFlags flags, DissectImageFlags flags,
@@ -5289,7 +5306,7 @@ int mountfsd_mount_image(
return log_error_errno(errno, "Failed to open '%s': %m", path); return log_error_errno(errno, "Failed to open '%s': %m", path);
_cleanup_(dissected_image_unrefp) DissectedImage *di = NULL; _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) if (r < 0)
return r; return r;
+2 -2
View File
@@ -268,8 +268,8 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m
int get_common_dissect_directory(char **ret); 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_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 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_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); int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd);
+37
View File
@@ -91,6 +91,43 @@ if [ "$VERITY_SIG_SUPPORTED" -eq 1 ]; then
--property ExtensionImagePolicy=root=verity+signed+absent:usr=verity+signed+absent \ --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\"") 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 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 fi
# Bare squashfs without any verity or signature also should be rejected, even if we ask to trust it # Bare squashfs without any verity or signature also should be rejected, even if we ask to trust it
+1
View File
@@ -25,6 +25,7 @@ at_exit() {
done < <(find "${IMAGE_DIR}" -mindepth 1 -maxdepth 1 -type d) done < <(find "${IMAGE_DIR}" -mindepth 1 -maxdepth 1 -type d)
rm -rf "$IMAGE_DIR" rm -rf "$IMAGE_DIR"
rm -rf /etc/polkit-1/rules.d/mountoptions.rules
rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules rm -f /etc/polkit-1/rules.d/sysext-unpriv.rules