udisks2/udisks-2.11.2-mount_as_user_auth.patch
2026-08-12 05:47:04 -04:00

890 lines
38 KiB
Diff

From 6605473b3e47a1b419c434f87e9c21aaf0be9783 Mon Sep 17 00:00:00 2001
From: Tomas Bzatek <tbzatek@redhat.com>
Date: Tue, 28 Apr 2026 19:10:09 +0200
Subject: [PATCH 1/5] udiskslinuxfilesystem: Separate real caller identity from
as-user target
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When the 'as-user' option is specified in Filesystem.Mount(), the code
previously overwrote caller_uid/caller_gid with the target user's identity.
This meant downstream functions received a uid they believed was the
D-Bus caller's, but was actually the target's.
Introduce effective_uid/effective_gid/effective_user_name to hold the
as-user target identity (or the caller's identity when as-user is not
set). The real D-Bus caller identity is now always resolved into
caller_uid/caller_gid and used for authorization-related decisions
(setup_by_user, on_user_seat checks), while effective_* is used for
mount point calculation, run_as_uid/run_as_gid, and state tracking.
Reported-by: Azizcan Daştan <azizcan.dastan5@gmail.com>
Reported-by: Özlem Ozan <oozan1725@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
src/udiskslinuxfilesystem.c | 93 +++++++++++++++++++++----------------
1 file changed, 53 insertions(+), 40 deletions(-)
diff --git a/src/udiskslinuxfilesystem.c b/src/udiskslinuxfilesystem.c
index 4f03c17e..41bdc317 100644
--- a/src/udiskslinuxfilesystem.c
+++ b/src/udiskslinuxfilesystem.c
@@ -906,7 +906,8 @@ handle_mount_fstab (UDisksDaemon *daemon,
UDisksObject *object,
uid_t caller_uid,
gid_t caller_gid,
- gboolean mount_other_user,
+ uid_t effective_uid,
+ gid_t effective_gid,
const gchar *mount_point_to_use,
const gchar *fstab_mount_options,
GDBusMethodInvocation *invocation,
@@ -937,7 +938,7 @@ handle_mount_fstab (UDisksDaemon *daemon,
* will be replaced by the name of the drive/device in question
*/
message = N_("Authentication is required to mount $(drive)");
- if (mount_other_user)
+ if (caller_uid != effective_uid)
{
action_id = "org.freedesktop.udisks2.filesystem-mount-other-user";
}
@@ -982,15 +983,15 @@ handle_mount_fstab (UDisksDaemon *daemon,
job = udisks_daemon_launch_simple_job (daemon,
UDISKS_OBJECT (object),
"filesystem-mount",
- mount_fstab_as_root ? 0 : caller_uid,
+ mount_fstab_as_root ? 0 : effective_uid,
FALSE,
NULL /* cancellable */);
/* XXX: using run_as_uid for root doesn't work even if the caller is already root */
- if (!mount_fstab_as_root && caller_uid != 0)
+ if (!mount_fstab_as_root && effective_uid != 0)
{
- BDExtraArg uid_arg = { g_strdup ("run_as_uid"), g_strdup_printf ("%d", caller_uid) };
- BDExtraArg gid_arg = { g_strdup ("run_as_gid"), g_strdup_printf ("%d", caller_gid) };
+ BDExtraArg uid_arg = { g_strdup ("run_as_uid"), g_strdup_printf ("%d", effective_uid) };
+ BDExtraArg gid_arg = { g_strdup ("run_as_gid"), g_strdup_printf ("%d", effective_gid) };
const BDExtraArg *extra_args[3] = { &uid_arg, &gid_arg, NULL };
success = bd_fs_mount (NULL, mount_point_to_use, NULL, NULL, extra_args, &error);
@@ -1068,8 +1069,9 @@ handle_mount_dynamic (UDisksDaemon *daemon,
UDisksObject *object,
uid_t caller_uid,
gid_t caller_gid,
- const gchar *caller_user_name,
- gboolean mount_other_user,
+ uid_t effective_uid,
+ gid_t effective_gid,
+ const gchar *effective_user_name,
gchar **mount_point_to_use,
gboolean *mpoint_persistent,
GDBusMethodInvocation *invocation,
@@ -1129,7 +1131,7 @@ handle_mount_dynamic (UDisksDaemon *daemon,
* will be replaced by the name of the drive/device in question
*/
message = N_("Authentication is required to mount $(drive)");
- if (mount_other_user)
+ if (caller_uid != effective_uid)
{
action_id = "org.freedesktop.udisks2.filesystem-mount-other-user";
}
@@ -1163,9 +1165,9 @@ handle_mount_dynamic (UDisksDaemon *daemon,
/* Calculate mount point (guaranteed to be valid UTF-8) */
*mount_point_to_use = calculate_mount_point (daemon,
block,
- caller_uid,
- caller_gid,
- caller_user_name,
+ effective_uid,
+ effective_gid,
+ effective_user_name,
fs_type_to_use,
mpoint_persistent,
&error);
@@ -1180,7 +1182,7 @@ handle_mount_dynamic (UDisksDaemon *daemon,
/* Calculate mount options (guaranteed to be valid UTF-8) */
mount_options = udisks_linux_calculate_mount_options (daemon,
block,
- caller_uid,
+ effective_uid,
fs_signature,
fs_type_to_use,
options,
@@ -1261,14 +1263,16 @@ handle_mount (UDisksFilesystem *filesystem,
UDisksBlock *block;
UDisksDaemon *daemon;
UDisksState *state = NULL;
- gchar *opt_as_user = NULL;
+ const gchar *opt_as_user = NULL;
uid_t caller_uid;
gid_t caller_gid;
+ uid_t effective_uid = 0;
+ gid_t effective_gid = 0;
+ gchar *effective_user_name = NULL;
const gchar * const *existing_mount_points;
gchar *mount_point_to_use = NULL;
gboolean mpoint_persistent = TRUE;
gchar *fstab_mount_options = NULL;
- gchar *caller_user_name = NULL;
GError *error = NULL;
gboolean system_managed = FALSE;
gchar *device = NULL;
@@ -1326,35 +1330,42 @@ handle_mount (UDisksFilesystem *filesystem,
goto out;
}
+ /* Always resolve the real D-Bus caller identity */
+ if (!udisks_daemon_util_get_caller_uid_sync (daemon,
+ invocation,
+ NULL /* GCancellable */,
+ &caller_uid,
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_clear_error (&error);
+ goto out;
+ }
+
+ if (!udisks_daemon_util_get_user_info (caller_uid,
+ &caller_gid,
+ opt_as_user ? NULL : &effective_user_name,
+ &error))
+ {
+ g_dbus_method_invocation_return_gerror (invocation, error);
+ g_clear_error (&error);
+ goto out;
+ }
+
if (opt_as_user)
{
- if (!udisks_daemon_util_get_user_info_by_name (opt_as_user, &caller_uid, &caller_gid, &error))
+ if (!udisks_daemon_util_get_user_info_by_name (opt_as_user, &effective_uid, &effective_gid, &error))
{
g_dbus_method_invocation_return_gerror (invocation, error);
g_clear_error (&error);
goto out;
}
- caller_user_name = g_strdup (opt_as_user);
+ effective_user_name = g_strdup (opt_as_user);
}
else
{
- if (!udisks_daemon_util_get_caller_uid_sync (daemon,
- invocation,
- NULL /* GCancellable */,
- &caller_uid,
- &error))
- {
- g_dbus_method_invocation_return_gerror (invocation, error);
- g_clear_error (&error);
- goto out;
- }
-
- if (!udisks_daemon_util_get_user_info (caller_uid, &caller_gid, &caller_user_name, &error))
- {
- g_dbus_method_invocation_return_gerror (invocation, error);
- g_clear_error (&error);
- goto out;
- }
+ effective_uid = caller_uid;
+ effective_gid = caller_gid;
}
/* Mount it */
@@ -1364,7 +1375,8 @@ handle_mount (UDisksFilesystem *filesystem,
object,
caller_uid,
caller_gid,
- opt_as_user != NULL,
+ effective_uid,
+ effective_gid,
mount_point_to_use,
fstab_mount_options,
invocation,
@@ -1377,8 +1389,9 @@ handle_mount (UDisksFilesystem *filesystem,
object,
caller_uid,
caller_gid,
- caller_user_name,
- opt_as_user != NULL,
+ effective_uid,
+ effective_gid,
+ effective_user_name,
&mount_point_to_use,
&mpoint_persistent,
invocation,
@@ -1390,7 +1403,7 @@ handle_mount (UDisksFilesystem *filesystem,
udisks_state_add_mounted_fs (state,
mount_point_to_use,
udisks_block_get_device_number (block),
- caller_uid,
+ effective_uid,
system_managed,
system_managed ? FALSE : mpoint_persistent);
@@ -1398,7 +1411,7 @@ handle_mount (UDisksFilesystem *filesystem,
device,
system_managed ? " (system)" : "",
mount_point_to_use,
- caller_uid);
+ effective_uid);
udisks_linux_block_object_trigger_uevent_sync (UDISKS_LINUX_BLOCK_OBJECT (object),
UDISKS_DEFAULT_WAIT_TIMEOUT);
@@ -1412,7 +1425,7 @@ handle_mount (UDisksFilesystem *filesystem,
udisks_state_check (state);
g_free (mount_point_to_use);
g_free (fstab_mount_options);
- g_free (caller_user_name);
+ g_free (effective_user_name);
g_free (device);
g_clear_object (&object);
--
2.54.0
From 07d083bf0f3213e68229f1fc8668952a719b816a Mon Sep 17 00:00:00 2001
From: Tomas Bzatek <tbzatek@redhat.com>
Date: Tue, 28 Apr 2026 19:29:14 +0200
Subject: [PATCH 2/5] udiskslinuxfilesystem: Rework fstab mount authorization
for as-user
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The polkit authorization logic in handle_mount_fstab() had two problems
when 'as-user' was combined with fstab entries containing 'user',
'users' or 'x-udisks-auth' mount options:
1. The 'filesystem-mount-other-user' polkit check was inside the code
block that is skipped when 'user'/'users'/'x-udisks-auth' options
are present, allowing an unprivileged caller to mount on behalf of
another user without any authorization.
2. When 'user' or 'users' fstab options were present and the initial
mount attempt failed with a permission error, the code would fall
back to mounting as root. This fallback should only be available
when 'x-udisks-auth' is explicitly specified.
Restructure the authorization flow so that:
- The 'filesystem-mount-other-user' check is evaluated first,
independently of fstab mount options, and is always enforced
when caller_uid != effective_uid.
- The root fallback on BD_FS_ERROR_AUTH is gated on 'x-udisks-auth'
being present, not just any of the user-level mount options.
- When root uses 'as-user' without 'user'/'users' fstab options,
the root fallback is implicitly enabled.
- Regular polkit checks (filesystem-mount, mount-system,
mount-other-seat) only apply for same-user mounts without
'user'/'users'/'x-udisks-auth'.
Update the D-Bus API documentation to reflect the new behavior.
Reported-by: Azizcan Daştan <azizcan.dastan5@gmail.com>
Reported-by: Özlem Ozan <oozan1725@gmail.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
data/org.freedesktop.UDisks2.xml | 24 ++++++-----
src/udiskslinuxfilesystem.c | 73 ++++++++++++++++++++++++--------
2 files changed, 70 insertions(+), 27 deletions(-)
diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml
index 184340ac..ab9e848d 100644
--- a/data/org.freedesktop.UDisks2.xml
+++ b/data/org.freedesktop.UDisks2.xml
@@ -2711,7 +2711,8 @@
filesystem is mounted on behalf of the specified user instead
of the calling one. This has usually an effect on the returned
@mount_path and it also allows that user to unmount the
- filesystem later. This option expects a user name, not a UID.
+ filesystem later. This option expects a user name, not a UID
+ and always performs an authorization check.
If the device in question is referenced in the
<filename>/etc/fstab</filename> file, the
@@ -2719,15 +2720,18 @@
and the given options or filesystem type given in @options are
ignored.
- If <literal>x-udisks-auth</literal> is specified as an option
- for the device in the <filename>/etc/fstab</filename> file,
- then the <command>mount</command> command is run as the
- calling user, without performing any authorization check
- mentioned above. If this fails because of insufficient
- permissions, an authorization check is performed (which
- typically results in the user having to authenticate as an
- administrator). If authorized, the <command>mount</command>
- command is then run as root.
+ If <literal>x-udisks-auth</literal>, <literal>user</literal> or
+ <literal>users</literal> are specified as options for the
+ device in the <filename>/etc/fstab</filename> file, then the
+ <command>mount</command> command is run as the calling user
+ (or the effective user of the <parameter>as-user</parameter>
+ option respectively), without performing any authorization check
+ mentioned above. If this fails because of insufficient permissions
+ and <literal>x-udisks-auth</literal> is specified, an
+ authorization check is performed (which typically results in
+ the user having to authenticate as an administrator). If
+ authorized, the <command>mount</command> command is then run
+ as root.
The filesystem should be unmounted using the
org.freedesktop.UDisks2.Filesystem.Unmount() method.
diff --git a/src/udiskslinuxfilesystem.c b/src/udiskslinuxfilesystem.c
index 41bdc317..ea449cd2 100644
--- a/src/udiskslinuxfilesystem.c
+++ b/src/udiskslinuxfilesystem.c
@@ -919,30 +919,60 @@ handle_mount_fstab (UDisksDaemon *daemon,
const gchar *message = NULL;
gboolean success = FALSE;
gboolean mount_fstab_as_root = FALSE;
+ gboolean x_udisks_auth = FALSE;
+ gboolean user_mount = FALSE;
UDisksBaseJob *job = NULL;
GError *error = NULL;
block = udisks_object_peek_block (object);
device = udisks_block_get_device (block);
- if (!has_option (fstab_mount_options, "x-udisks-auth") &&
- !has_option (fstab_mount_options, "user") &&
- !has_option (fstab_mount_options, "users"))
+ x_udisks_auth = has_option (fstab_mount_options, "x-udisks-auth");
+ user_mount = x_udisks_auth ||
+ has_option (fstab_mount_options, "user") ||
+ has_option (fstab_mount_options, "users");
+
+ /* 'as-user' rules:
+ * - when caller is root:
+ * - if 'user'/'users' present, do not fall back to mounting as root, unless 'x-udisks-auth' is specified
+ * - if 'user'/'users' not present, try as effective user first and allow fallback to mounting as root (implies 'x-udisks-auth')
+ * - when caller is unprivileged:
+ * - always require polkit auth to prevent identity spoofing
+ * - do not fall back to mounting as root, unless 'x-udisks-auth' is specified
+ * - if neither 'user'/'users'/'x-udisks-auth' present, failure is expected by nature (unless 'as-user=root')
+ *
+ * general fstab mounting rules:
+ * - when caller is root, mount as root, allow anything
+ * - when caller is unprivileged:
+ * - if 'user'/'users' present, do not fall back to mounting as root, unless 'x-udisks-auth' is specified
+ * - if only 'x-udisks-auth' present, mount as user and fall back to mounting as root if not successful
+ * - if neither 'user'/'users'/'x-udisks-auth' present, require polkit auth and mount as root
+ *
+ * Further assumptions:
+ * - when 'user'/'users' not present and mounting as unprivileged, this typically fails but it is still beneficial to try that e.g. for FUSE mounts
+ * - assuming libblockdev mount error code mapping works reliably (for the BD_FS_ERROR_AUTH check)
+ */
+
+ if (caller_uid != effective_uid)
+ {
+ /* Always require authorization when mounting on behalf of another user,
+ * regardless of fstab mount options.
+ */
+ action_id = "org.freedesktop.udisks2.filesystem-mount-other-user";
+
+ /* When 'user'/'users' not present and the caller is root, allow
+ * fallback mounting as root.
+ */
+ if (caller_uid == 0 && !user_mount)
+ x_udisks_auth = TRUE;
+ }
+ else
+ if (!user_mount)
{
mount_fstab_as_root = TRUE;
+
action_id = "org.freedesktop.udisks2.filesystem-mount";
- /* Translators: Shown in authentication dialog when the user
- * requests mounting a filesystem.
- *
- * Do not translate $(drive), it's a placeholder and
- * will be replaced by the name of the drive/device in question
- */
- message = N_("Authentication is required to mount $(drive)");
- if (caller_uid != effective_uid)
- {
- action_id = "org.freedesktop.udisks2.filesystem-mount-other-user";
- }
- else if (!udisks_daemon_util_setup_by_user (daemon, object, caller_uid))
+ if (!udisks_daemon_util_setup_by_user (daemon, object, caller_uid))
{
if (udisks_block_get_hint_system (block))
{
@@ -953,7 +983,17 @@ handle_mount_fstab (UDisksDaemon *daemon,
action_id = "org.freedesktop.udisks2.filesystem-mount-other-seat";
}
}
+ }
+ if (action_id)
+ {
+ /* Translators: Shown in authentication dialog when the user
+ * requests mounting a filesystem.
+ *
+ * Do not translate $(device.name), it's a placeholder and
+ * will be replaced by the name of the drive/device in question
+ */
+ message = N_("Authentication is required to mount $(device.name)");
if (!udisks_daemon_util_check_authorization_sync (daemon,
object,
action_id,
@@ -963,7 +1003,6 @@ handle_mount_fstab (UDisksDaemon *daemon,
return FALSE;
}
-
if (!g_file_test (mount_point_to_use, G_FILE_TEST_IS_DIR))
{
if (g_mkdir_with_parents (mount_point_to_use, 0755) != 0)
@@ -1013,7 +1052,7 @@ handle_mount_fstab (UDisksDaemon *daemon,
if (!success)
{
- if (!mount_fstab_as_root && g_error_matches (error, BD_FS_ERROR, BD_FS_ERROR_AUTH))
+ if (!mount_fstab_as_root && g_error_matches (error, BD_FS_ERROR, BD_FS_ERROR_AUTH) && x_udisks_auth)
{
g_clear_error (&error);
action_id = "org.freedesktop.udisks2.filesystem-fstab";
--
2.54.0
From bf1bc55e559c4e42ca39591714a8582790846838 Mon Sep 17 00:00:00 2001
From: Tomas Bzatek <tbzatek@redhat.com>
Date: Sun, 3 May 2026 14:00:01 +0200
Subject: [PATCH 3/5] udiskslinuxfilesystem: Log real caller uid for as-user
mounts
When a mount is performed with the as-user option targeting a different
user, log both the effective (target) uid and the real caller uid to
provide an audit trail of who authorized the operation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
src/udiskslinuxfilesystem.c | 22 +++++++++++++++++-----
1 file changed, 17 insertions(+), 5 deletions(-)
diff --git a/src/udiskslinuxfilesystem.c b/src/udiskslinuxfilesystem.c
index ea449cd2..e591c821 100644
--- a/src/udiskslinuxfilesystem.c
+++ b/src/udiskslinuxfilesystem.c
@@ -1446,11 +1446,23 @@ handle_mount (UDisksFilesystem *filesystem,
system_managed,
system_managed ? FALSE : mpoint_persistent);
- udisks_info ("Mounted %s%s at %s on behalf of uid %u",
- device,
- system_managed ? " (system)" : "",
- mount_point_to_use,
- effective_uid);
+ if (effective_uid != caller_uid)
+ {
+ udisks_info ("Mounted %s%s at %s on behalf of uid %u (requested by uid %u)",
+ device,
+ system_managed ? " (system)" : "",
+ mount_point_to_use,
+ effective_uid,
+ caller_uid);
+ }
+ else
+ {
+ udisks_info ("Mounted %s%s at %s on behalf of uid %u",
+ device,
+ system_managed ? " (system)" : "",
+ mount_point_to_use,
+ effective_uid);
+ }
udisks_linux_block_object_trigger_uevent_sync (UDISKS_LINUX_BLOCK_OBJECT (object),
UDISKS_DEFAULT_WAIT_TIMEOUT);
--
2.54.0
From 99c5efc4c40812dc04625e1ebc963a2b49b14613 Mon Sep 17 00:00:00 2001
From: Tomas Bzatek <tbzatek@redhat.com>
Date: Tue, 28 Apr 2026 19:10:38 +0200
Subject: [PATCH 4/5] udisksdaemonutil: Pass as-user target to polkit details
When the 'as-user' D-Bus method option is present, propagate the
target username to polkit details as 'mount.as-user'. This allows
polkit rules to make fine-grained authorization decisions based on
who the mount is being performed for.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
src/udisksdaemonutil.c | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/udisksdaemonutil.c b/src/udisksdaemonutil.c
index 3db92f58..36e3184b 100644
--- a/src/udisksdaemonutil.c
+++ b/src/udisksdaemonutil.c
@@ -797,6 +797,14 @@ udisks_daemon_util_check_authorization_sync_with_error (UDisksDaemon *
polkit_details_insert (details, "polkit.message", message);
polkit_details_insert (details, "polkit.gettext_domain", "udisks2");
+ if (options != NULL &&
+ g_strcmp0 (action_id, "org.freedesktop.udisks2.filesystem-mount-other-user") == 0)
+ {
+ const gchar *as_user = NULL;
+ g_variant_lookup (options, "as-user", "&s", &as_user);
+ _safe_polkit_details_insert (details, "mount.as-user", as_user);
+ }
+
/* Find drive associated with the block device, if any */
if (object != NULL)
{
--
2.54.0
From 40bd25d60d9551b67603dc732828109e0c3beaed Mon Sep 17 00:00:00 2001
From: Tomas Bzatek <tbzatek@redhat.com>
Date: Wed, 29 Apr 2026 16:36:45 +0200
Subject: [PATCH 5/5] tests: Add security tests for as-user mount authorization
Add tests verifying that an unprivileged D-Bus caller cannot exploit
the 'as-user' option in Filesystem.Mount() to mount on behalf of
another user without polkit authorization. Use a dedicated
'udisks_test_target' user instead of 'nobody' to avoid OS-level
special treatment of system users that could mask real behavior.
Dynamic mount tests (in UdisksFSTestCase, inherited by all FS types):
- test_mount_as_user_denied: as-user=<another_user> is denied
- test_mount_as_user_root_denied: as-user=root is denied
- test_mount_as_user_self: as-user=<self> behaves like plain mount
Fstab mount tests (in NonPOSIXTestCase, inherited by VFAT/EXFAT):
- test_mount_fstab_{users,user,x-udisks-auth}_as_user_denied:
fstab entries that normally allow unprivileged mounting must still
require authorization when as-user is specified
- test_mount_fstab_{users,user,x-udisks-auth}_as_root_denied:
same but with as-user=root
- test_mount_fstab_defaults_as_user_from_root: root can mount
fstab 'defaults' device with as-user targeting a non-root user
- test_mount_fstab_defaults_as_user_denied: unprivileged user
cannot mount fstab 'defaults' device with as-user
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
src/tests/dbus-tests/test_80_filesystem.py | 278 +++++++++++++++++++++
1 file changed, 278 insertions(+)
diff --git a/src/tests/dbus-tests/test_80_filesystem.py b/src/tests/dbus-tests/test_80_filesystem.py
index 75865cce..990d9eb8 100644
--- a/src/tests/dbus-tests/test_80_filesystem.py
+++ b/src/tests/dbus-tests/test_80_filesystem.py
@@ -29,6 +29,8 @@ class UdisksFSTestCase(udiskstestcase.UdisksTestCase):
_can_query_size = False
username = 'udisks_test_user'
+ # a separate target user used for 'as-user' mount tests
+ target_username = 'udisks_test_target'
requested_plugins = BlockDev.plugin_specs_from_names(("fs",))
@@ -1112,6 +1114,174 @@ class UdisksFSTestCase(udiskstestcase.UdisksTestCase):
pipe.send([True, ''])
pipe.close()
+ def _mount_with_as_user_fail(self, pipe, uid, gid, device, target_user):
+ """ Try to mount @device as user with given @uid and @gid using the
+ 'as-user' option set to @target_user. This is expected to fail
+ with a NotAuthorized error.
+ """
+ os.setresgid(gid, gid, gid)
+ os.setresuid(uid, uid, uid)
+
+ try:
+ safe_dbus.call_sync(self.iface_prefix,
+ self.path_prefix + '/block_devices/' + os.path.basename(device),
+ self.iface_prefix + '.Filesystem',
+ 'Mount',
+ GLib.Variant('(a{sv})', ({'as-user': GLib.Variant('s', target_user)},)))
+ except Exception as e:
+ msg = str(e)
+ if 'org.freedesktop.UDisks2.Error.NotAuthorized' in msg and \
+ 'Not authorized to perform operation' in msg:
+ pipe.send([True, ''])
+ pipe.close()
+ return
+ else:
+ pipe.send([False, 'Mount DBus call failed with unexpected exception: %s' % msg])
+ pipe.close()
+ return
+
+ pipe.send([False, 'Mount with as-user=%s unexpectedly succeeded for uid %d' % (target_user, uid)])
+ pipe.close()
+
+ def _test_mount_as_user_denied(self, target_user):
+ """ Test that an unprivileged user cannot use as-user to mount on
+ behalf of @target_user without polkit authorization.
+ """
+ self._check_can_create()
+
+ if not self._can_mount:
+ self.skipTest('Cannot mount %s filesystem' % self._fs_signature)
+
+ disk = self.get_object('/block_devices/' + os.path.basename(self.vdevs[0]))
+ self.assertIsNotNone(disk)
+
+ disk.Format(self._fs_signature, self.no_options, dbus_interface=self.iface_prefix + '.Block')
+ self.addCleanup(self.wipe_fs, self.vdevs[0])
+ self.addCleanup(self.try_unmount, self.vdevs[0])
+
+ self.addCleanup(self._remove_user, self.username)
+ uid, gid = self._add_user(self.username)
+
+ if target_user != 'root':
+ self.addCleanup(self._remove_user, target_user)
+ self._add_user(target_user)
+
+ parent_conn, child_conn = Pipe()
+ proc = Process(target=self._mount_with_as_user_fail,
+ args=(child_conn, int(uid), int(gid), self.vdevs[0], target_user))
+ proc.start()
+ res = parent_conn.recv()
+ parent_conn.close()
+ proc.join()
+
+ if not res[0]:
+ self.fail(res[1])
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_as_user_denied(self):
+ """ Test that an unprivileged user cannot use as-user to mount on
+ behalf of another user without authorization.
+ """
+ self._test_mount_as_user_denied(self.target_username)
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_as_user_root_denied(self):
+ """ Test that an unprivileged user cannot use as-user=root to mount
+ without authorization.
+ """
+ self._test_mount_as_user_denied('root')
+
+ def _mount_as_user_result(self, pipe, uid, gid, device, options):
+ """ Try to mount @device as user with given @uid and @gid using
+ the given @options dict. Returns [True, error_string] on D-Bus
+ error or [True, ''] on success.
+ """
+ os.setresgid(gid, gid, gid)
+ os.setresuid(uid, uid, uid)
+
+ try:
+ safe_dbus.call_sync(self.iface_prefix,
+ self.path_prefix + '/block_devices/' + os.path.basename(device),
+ self.iface_prefix + '.Filesystem',
+ 'Mount',
+ GLib.Variant('(a{sv})', (options,)))
+ except Exception as e:
+ pipe.send([True, str(e)])
+ pipe.close()
+ return
+
+ pipe.send([True, ''])
+ pipe.close()
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_as_user_self(self):
+ """ Test that as-user targeting the caller's own username behaves
+ identically to mounting without as-user (no extra mount-other-user
+ authorization required).
+ """
+ self._check_can_create()
+
+ if not self._can_mount:
+ self.skipTest('Cannot mount %s filesystem' % self._fs_signature)
+
+ disk = self.get_object('/block_devices/' + os.path.basename(self.vdevs[0]))
+ self.assertIsNotNone(disk)
+
+ disk.Format(self._fs_signature, self.no_options, dbus_interface=self.iface_prefix + '.Block')
+ self.addCleanup(self.wipe_fs, self.vdevs[0])
+ self.addCleanup(self.try_unmount, self.vdevs[0])
+
+ self.addCleanup(self._remove_user, self.username)
+ uid, gid = self._add_user(self.username)
+
+ # first try a plain mount without as-user
+ parent_conn, child_conn = Pipe()
+ proc = Process(target=self._mount_as_user_result,
+ args=(child_conn, int(uid), int(gid), self.vdevs[0], {}))
+ proc.start()
+ res_plain = parent_conn.recv()
+ parent_conn.close()
+ proc.join()
+
+ self.assertTrue(res_plain[0])
+ plain_error = res_plain[1]
+
+ # unmount if the plain mount succeeded
+ if not plain_error:
+ self.try_unmount(self.vdevs[0])
+
+ # now try with as-user=<self>
+ parent_conn, child_conn = Pipe()
+ proc = Process(target=self._mount_as_user_result,
+ args=(child_conn, int(uid), int(gid), self.vdevs[0],
+ {'as-user': GLib.Variant('s', self.username)}))
+ proc.start()
+ res_as_user = parent_conn.recv()
+ parent_conn.close()
+ proc.join()
+
+ self.assertTrue(res_as_user[0])
+ as_user_error = res_as_user[1]
+
+ if not plain_error and not as_user_error:
+ return
+ if plain_error and as_user_error:
+ # extract the GDBus error name (e.g. 'org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain')
+ dbus_err_re = re.compile(r'GDBus\.Error:([\w.]+):')
+ plain_match = dbus_err_re.search(plain_error)
+ as_user_match = dbus_err_re.search(as_user_error)
+ plain_err_name = plain_match.group(1) if plain_match else plain_error
+ as_user_err_name = as_user_match.group(1) if as_user_match else as_user_error
+ self.assertEqual(plain_err_name, as_user_err_name,
+ 'as-user=<self> produced a different error than plain mount: '
+ 'plain=%s, as-user=%s' % (plain_error, as_user_error))
+ return
+
+ if plain_error:
+ self.fail('Plain mount failed (%s) but as-user=<self> succeeded' % plain_error)
+ else:
+ self.fail('Plain mount succeeded but as-user=<self> failed: %s' % as_user_error)
+
@udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
def test_mount_as_user(self):
""" Test mounting a filesystem on behalf of a different user."""
@@ -1624,6 +1794,108 @@ class NonPOSIXTestCase(UdisksFSTestCase):
self.fail(res[1])
+ def _test_mount_fstab_as_user_denied(self, fstab_options, target_user):
+ """ Test that an unprivileged user cannot use 'as-user' option to mount
+ a device listed in /etc/fstab with @fstab_options on behalf of
+ @target_user without polkit authorization.
+ """
+ disk = self.get_object('/block_devices/' + os.path.basename(self.vdevs[0]))
+ self.assertIsNotNone(disk)
+
+ self.addCleanup(self._remove_user, self.username)
+ uid, gid = self._add_user(self.username)
+
+ if target_user != 'root':
+ self.addCleanup(self._remove_user, target_user)
+ self._add_user(target_user)
+
+ self.addCleanup(self.try_unmount, self.vdevs[0])
+ self._prepare_mount_test(disk, True, fstab_options)
+
+ parent_conn, child_conn = Pipe()
+ proc = Process(target=self._mount_with_as_user_fail,
+ args=(child_conn, int(uid), int(gid), self.vdevs[0], target_user))
+ proc.start()
+ res = parent_conn.recv()
+ parent_conn.close()
+ proc.join()
+
+ if not res[0]:
+ self.fail(res[1])
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_as_user_denied(self):
+ """ Test that user cannot use as-user to mount fstab 'users' device
+ on behalf of another user without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('users', self.target_username)
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_user_as_user_denied(self):
+ """ Test that user cannot use as-user to mount fstab 'user' device
+ on behalf of another user without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('user', self.target_username)
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_x_udisks_auth_as_user_denied(self):
+ """ Test that user cannot use as-user to mount fstab 'x-udisks-auth'
+ device on behalf of another user without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('x-udisks-auth', self.target_username)
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_as_root_denied(self):
+ """ Test that user cannot use as-user=root to mount fstab 'users'
+ device without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('users', 'root')
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_user_as_root_denied(self):
+ """ Test that user cannot use as-user=root to mount fstab 'user'
+ device without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('user', 'root')
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_x_udisks_auth_as_root_denied(self):
+ """ Test that user cannot use as-user=root to mount fstab
+ 'x-udisks-auth' device without authorization.
+ """
+ self._test_mount_fstab_as_user_denied('x-udisks-auth', 'root')
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_defaults_as_user_from_root(self):
+ """ Test that root can mount a device listed in /etc/fstab with
+ 'defaults' (no 'user', 'users' or 'x-udisks-auth' options)
+ using the 'as-user' option targeting a non-root user.
+ """
+ disk = self.get_object('/block_devices/' + os.path.basename(self.vdevs[0]))
+ self.assertIsNotNone(disk)
+
+ self.addCleanup(self._remove_user, self.target_username)
+ self._add_user(self.target_username)
+
+ self.addCleanup(self.try_unmount, self.vdevs[0])
+ self._prepare_mount_test(disk, True, 'defaults')
+
+ d = dbus.Dictionary(signature='sv')
+ d['as-user'] = self.target_username
+ mnt_path = disk.Mount(d, dbus_interface=self.iface_prefix + '.Filesystem')
+ self.assertIsNotNone(mnt_path)
+ self.assertTrue(os.path.ismount(mnt_path))
+
+ @udiskstestcase.tag_test(udiskstestcase.TestTags.UNSAFE)
+ def test_mount_fstab_defaults_as_user_denied(self):
+ """ Test that an unprivileged user cannot use 'as-user' option to
+ mount a device listed in /etc/fstab with 'defaults' (no 'user',
+ 'users' or 'x-udisks-auth') on behalf of another user without
+ polkit authorization.
+ """
+ self._test_mount_fstab_as_user_denied('defaults', self.target_username)
+
+
class VFATTestCase(NonPOSIXTestCase):
_fs_signature = 'vfat'
_can_mount = True
@@ -1776,6 +2048,12 @@ class UDFTestCase(UdisksFSTestCase):
def test_mount_fstab_complex_label_bad(self):
super(UDFTestCase, self).test_mount_fstab_complex_label_bad()
+ def test_mount_as_user_denied(self):
+ self.skipTest('Skipping as-user authorization test for UDF')
+
+ def test_mount_as_user_root_denied(self):
+ self.skipTest('Skipping as-user authorization test for UDF')
+
class FailsystemTestCase(UdisksFSTestCase):
# test that not supported operations fail 'nicely'
--
2.54.0