]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
libmount: accept X-mount.{owner,group,mode}=
authorнаб <nabijaczleweli@nabijaczleweli.xyz>
Tue, 19 Apr 2022 14:56:07 +0000 (16:56 +0200)
committerKarel Zak <kzak@redhat.com>
Thu, 21 Apr 2022 09:57:11 +0000 (11:57 +0200)
Which take a user, group, and mode, respectively, and set them on the
target after mounting

This is vaguely similar to tmpfs(5)'s [ug]id= and mode= options,
but we POSIX-parse the user- and group names

Oft requested in systemd/zram-generator, since a common use-case
is to use it to create /tmp or an equivalent directory that needs
to be a=rwx,o+t (or a user's private temp that needs to be owned
by them) ‒ this is impossible without terrible hacks, cf.
https://github.com/systemd/zram-generator/issues/150,
https://github.com/systemd/zram-generator/issues/146, &c.

This started off as a Set{User,Group,Mode}= systemd mount unit,
but was poetterung into libmount options:
https://github.com/systemd/systemd/pull/22889

Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
Signed-off-by: Karel Zak <kzak@redhat.com>
libmount/src/context.c
libmount/src/context_mount.c
libmount/src/libmount.h.in
libmount/src/mountP.h
libmount/src/optstr.c
libmount/src/utils.c
sys-utils/mount.8.adoc
tests/expected/mount/set_ugid_mode [new file with mode: 0644]
tests/ts/mount/set_ugid_mode [new file with mode: 0755]

index 99ca58b9ffc80ca1801053de060fd9614b20b577..7927012d2080b5afe9c6fb490899aa849fe6dd4c 100644 (file)
@@ -57,6 +57,10 @@ struct libmnt_context *mnt_new_context(void)
        if (!cxt)
                return NULL;
 
+       cxt->tgt_owner = (uid_t) -1;
+       cxt->tgt_group = (gid_t) -1;
+       cxt->tgt_mode = (mode_t) -1;
+
        INIT_LIST_HEAD(&cxt->addmounts);
 
        ruid = getuid();
@@ -76,7 +80,6 @@ struct libmnt_context *mnt_new_context(void)
        DBG(CXT, ul_debugobj(cxt, "----> allocate %s",
                                cxt->restricted ? "[RESTRICTED]" : ""));
 
-
        return cxt;
 }
 
@@ -130,7 +133,6 @@ void mnt_free_context(struct libmnt_context *cxt)
  *     mnt_context_set_options_pattern(cxt, NULL);
  *     mnt_context_set_target_ns(cxt, NULL);
  *
- *
  * to reset this stuff.
  *
  * Returns: 0 on success, negative number in case of error.
@@ -155,6 +157,10 @@ int mnt_reset_context(struct libmnt_context *cxt)
        free(cxt->orig_user);
        free(cxt->subdir);
 
+       cxt->tgt_owner = (uid_t) -1;
+       cxt->tgt_group = (gid_t) -1;
+       cxt->tgt_mode = (mode_t) -1;
+
        cxt->fs = NULL;
        cxt->mtab = NULL;
        cxt->utab = NULL;
@@ -3108,7 +3114,7 @@ static void close_ns(struct libmnt_ns *ns)
  *
  * This function sets errno to ENOSYS and returns error if libmount is
  * compiled without namespaces support.
-*
+ *
  * Returns: 0 on success, negative number in case of error.
  *
  * Since: 2.33
index f09e6886020d88abed163539f9df9b80417ea587..6af83e0412fa8128c14d8efcf8245d8609db1488 100644 (file)
@@ -837,7 +837,7 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type)
                cxt->syscall_status = 0;
 
                DBG(CXT, ul_debugobj(cxt, "FAKE mount(2) "
-                               "[source=%s, target=%s, type=%s, "
+                               "[source=%s, target=%s, type=%s,"
                                " mountflags=0x%08lx, mountdata=%s]",
                                src, target, type,
                                flags, cxt->mountdata ? "yes" : "<none>"));
@@ -862,7 +862,7 @@ static int do_mount(struct libmnt_context *cxt, const char *try_type)
                }
 
                DBG(CXT, ul_debugobj(cxt, "mount(2) "
-                       "[source=%s, target=%s, type=%s, "
+                       "[source=%s, target=%s, type=%s,"
                        " mountflags=0x%08lx, mountdata=%s]",
                        src, target, type,
                        flags, cxt->mountdata ? "yes" : "<none>"));
@@ -1020,6 +1020,54 @@ static int do_mount_by_pattern(struct libmnt_context *cxt, const char *pattern)
        return rc;
 }
 
+/*
+ * Process X-mount.owner=, X-mount.group=, X-mount.mode=.
+ */
+static int parse_ownership_mode(struct libmnt_context *cxt)
+{
+       int rc;
+       char *value;
+       size_t valsz;
+
+       const char *o = mnt_fs_get_user_options(cxt->fs);
+       if (!o)
+               return 0;
+
+       if ((rc = mnt_optstr_get_option(o, "X-mount.owner", &value, &valsz)) < 0)
+               return -MNT_ERR_MOUNTOPT;
+       if (!rc) {
+               if (!valsz)
+                       return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+               if (mnt_parse_uid(value, valsz, &cxt->tgt_owner))
+                       return -MNT_ERR_MOUNTOPT;
+       }
+
+       if ((rc = mnt_optstr_get_option(o, "X-mount.group", &value, &valsz)) < 0)
+               return -MNT_ERR_MOUNTOPT;
+       if (!rc) {
+               if (!valsz)
+                       return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+               if (mnt_parse_gid(value, valsz, &cxt->tgt_group))
+                       return -MNT_ERR_MOUNTOPT;
+       }
+
+       if ((rc = mnt_optstr_get_option(o, "X-mount.mode", &value, &valsz)) < 0)
+               return -MNT_ERR_MOUNTOPT;
+       if (!rc) {
+               if (!valsz)
+                       return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+               if ((rc = mnt_parse_mode(value, valsz, &cxt->tgt_mode)))
+                       return -MNT_ERR_MOUNTOPT;
+       }
+
+       DBG(CXT, ul_debugobj(cxt, "ownership %d:%d, mode %04o", cxt->tgt_owner, cxt->tgt_group, cxt->tgt_mode));
+
+       return 0;
+}
+
 /**
  * mnt_context_prepare_mount:
  * @cxt: context
@@ -1064,6 +1112,8 @@ int mnt_context_prepare_mount(struct libmnt_context *cxt)
                rc = mnt_context_guess_fstype(cxt);
        if (!rc)
                rc = mnt_context_prepare_target(cxt);
+       if (!rc)
+               rc = parse_ownership_mode(cxt);
        if (!rc)
                rc = mnt_context_prepare_helper(cxt, "mount", NULL);
 
@@ -1089,6 +1139,25 @@ end:
        return rc;
 }
 
+static int set_ownership_mode(struct libmnt_context *cxt)
+{
+       const char *target = mnt_fs_get_target(cxt->fs);
+
+       if (cxt->tgt_owner != (uid_t) -1 || cxt->tgt_group != (uid_t) -1) {
+               DBG(CXT, ul_debugobj(cxt, "mount: lchown(%s, %u, %u)", target, cxt->tgt_owner, cxt->tgt_group));
+               if (lchown(target, cxt->tgt_owner, cxt->tgt_group) == -1)
+                       return -MNT_ERR_CHOWN;
+       }
+
+       if (cxt->tgt_mode != (mode_t) -1) {
+               DBG(CXT, ul_debugobj(cxt, "mount: chmod(%s, %04o)", target, cxt->tgt_mode));
+               if (chmod(target, cxt->tgt_mode) == -1)
+                       return -MNT_ERR_CHMOD;
+       }
+
+       return 0;
+}
+
 /**
  * mnt_context_do_mount
  * @cxt: context
@@ -1191,6 +1260,9 @@ int mnt_context_do_mount(struct libmnt_context *cxt)
        if (mnt_context_is_veritydev(cxt))
                mnt_context_deferred_delete_veritydev(cxt);
 
+       if (!res)
+               res = set_ownership_mode(cxt);
+
        if (!mnt_context_switch_ns(cxt, ns_old))
                return -MNT_ERR_NAMESPACE;
 
@@ -1841,7 +1913,18 @@ int mnt_context_get_mount_excode(
                        if (buf)
                                snprintf(buf, bufsz, _("filesystem was mounted, but failed to switch namespace back"));
                        return MNT_EX_SYSERR;
+               }
+
+               if (rc == -MNT_ERR_CHOWN) {
+                       if (buf)
+                               snprintf(buf, bufsz, _("filesystem was mounted, but failed to change ownership to %u:%u: %m"), cxt->tgt_owner, cxt->tgt_group);
+                       return MNT_EX_SYSERR;
+               }
 
+               if (rc == -MNT_ERR_CHMOD) {
+                       if (buf)
+                               snprintf(buf, bufsz, _("filesystem was mounted, but failed to change mode to %04o: %m"), cxt->tgt_mode);
+                       return MNT_EX_SYSERR;
                }
 
                if (rc < 0)
index 43d8e44cebadf2c702477ed511278fa663ce58a5..0ab1d6ece7aee41d58c850a317f15d360a4b1dc2 100644 (file)
@@ -220,6 +220,18 @@ enum {
  * filesystem mounted, but --onlyonce specified
  */
 #define MNT_ERR_ONLYONCE    5010
+/**
+ * MNT_ERR_CHOWN:
+ *
+ * filesystem mounted, but subsequent X-mount.owner=/X-mount.group= lchown(2) failed
+ */
+#define MNT_ERR_CHOWN    5011
+/**
+ * MNT_ERR_CHMOD:
+ *
+ * filesystem mounted, but subsequent X-mount.mode= chmod(2) failed
+ */
+#define MNT_ERR_CHMOD    5012
 
 
 /*
@@ -246,7 +258,6 @@ enum {
  *
  * [u]mount(8) exit code: out of memory, cannot fork, ...
  */
-
 #define MNT_EX_SYSERR  2
 
 /**
index 561ddcd8c1c0f057b408608f9c10a997b965fc93..26158e8d948cc1e04bdcc4d01a2c8ba256ebf4d5 100644 (file)
@@ -109,6 +109,9 @@ extern int mnt_chdir_to_parent(const char *target, char **filename);
 extern char *mnt_get_username(const uid_t uid);
 extern int mnt_get_uid(const char *username, uid_t *uid);
 extern int mnt_get_gid(const char *groupname, gid_t *gid);
+extern int mnt_parse_uid(const char *user, size_t user_len, uid_t *gid);
+extern int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid);
+extern int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *gid);
 extern int mnt_in_group(gid_t gid);
 
 extern int mnt_open_uniq_filename(const char *filename, char **name);
@@ -294,6 +297,10 @@ struct libmnt_context
 
        char    *subdir;                /* X-mount.subdir= */
 
+       uid_t   tgt_owner;              /* X-mount.owner= */
+       gid_t   tgt_group;              /* X-mount.group= */
+       mode_t  tgt_mode;               /* X-mount.mode= */
+
        struct libmnt_fs *fs;           /* filesystem description (type, mountpoint, device, ...) */
        struct libmnt_fs *fs_template;  /* used for @fs on mnt_reset_context() */
 
index 43b983ebbc4b56aaaa03bdaffcd3c7c00b7f4f01..0ad5bfdc6d4a3331fc1e78f74f23b37b76f29a7c 100644 (file)
@@ -1019,15 +1019,13 @@ int mnt_optstr_fix_user(char **optstr)
 /*
  * Converts value from @optstr addressed by @name to uid.
  *
- * Returns: 0 on success, 1 if not found, <0 on error
+ * Returns: 0 on success, <0 on error
  */
 int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid)
 {
        char *value = NULL;
        size_t valsz = 0;
-       char buf[sizeof(stringify_value(UINT64_MAX))];
        int rc;
-       uint64_t num;
 
        assert(optstr);
        assert(name);
@@ -1037,20 +1035,11 @@ int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid)
        if (rc != 0)
                goto fail;
 
-       if (valsz > sizeof(buf) - 1) {
-               rc = -ERANGE;
+       rc = mnt_parse_uid(value, valsz, uid);
+       if (rc != 0) {
+               rc = -errno;
                goto fail;
        }
-       mem2strcpy(buf, value, valsz, sizeof(buf));
-
-       rc = ul_strtou64(buf, &num, 10);
-       if (rc != 0)
-               goto fail;
-       if (num > ULONG_MAX || (uid_t) num != num) {
-               rc = -ERANGE;
-               goto fail;
-       }
-       *uid = (uid_t) num;
 
        return 0;
 fail:
index ff3acfb559b2148b146549be6b873ffaa09f0321..a138abe7ee2624e3008776882991e7c0de81d563 100644 (file)
@@ -635,6 +635,132 @@ int mnt_get_gid(const char *groupname, gid_t *gid)
        return rc;
 }
 
+static int parse_uid_numeric(const char *value, size_t valsz, uid_t *uid)
+{
+       uint64_t num;
+
+       assert(value);
+       assert(uid);
+
+       if (valsz > sizeof(stringify_value(UINT64_MAX)) - 1) {
+               errno = ERANGE;
+               goto fail;
+       }
+
+       if (ul_strtou64(value, &num, 10) != 0) {
+               errno = ENOENT;
+               goto fail;
+       }
+       if (num > ULONG_MAX || (uid_t) num != num) {
+               errno = ERANGE;
+               goto fail;
+       }
+       *uid = (uid_t) num;
+
+       return 0;
+fail:
+       DBG(UTILS, ul_debug("failed to convert '%s' to number [errno=%d]", value, errno));
+       return -1;
+}
+
+/* POSIX-parse user_len-sized user; -1 and errno set, or 0 on success */
+int mnt_parse_uid(const char *user, size_t user_len, uid_t *uid)
+{
+       char *user_tofree = NULL;
+       int rc;
+
+       if (user[user_len]) {
+               user = user_tofree = strndup(user, user_len);
+               if (!user)
+                       return -1;
+       }
+
+       rc = mnt_get_uid(user, uid);
+       if (rc != 0 && isdigit(*user))
+               rc = parse_uid_numeric(user, user_len, uid);
+
+       free(user_tofree);
+       return rc;
+}
+
+static int parse_gid_numeric(const char *value, size_t valsz, gid_t *gid)
+{
+       uint64_t num;
+
+       assert(value);
+       assert(gid);
+
+       if (valsz > sizeof(stringify_value(UINT64_MAX)) - 1) {
+               errno = ERANGE;
+               goto fail;
+       }
+
+       if (ul_strtou64(value, &num, 10) != 0) {
+               errno = ENOENT;
+               goto fail;
+       }
+       if (num > ULONG_MAX || (gid_t) num != num) {
+               errno = ERANGE;
+               goto fail;
+       }
+       *gid = (gid_t) num;
+
+       return 0;
+fail:
+       DBG(UTILS, ul_debug("failed to convert '%s' to number [errno=%d]", value, errno));
+       return -1;
+}
+
+/* POSIX-parse group_len-sized group; -1 and errno set, or 0 on success */
+int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid)
+{
+       char *group_tofree = NULL;
+       int rc;
+
+       if (group[group_len]) {
+               group = group_tofree = strndup(group, group_len);
+               if (!group)
+                       return -1;
+       }
+
+       rc = mnt_get_gid(group, gid);
+       if (rc != 0 && isdigit(*group))
+               rc = parse_gid_numeric(group, group_len, gid);
+
+       free(group_tofree);
+       return rc;
+}
+
+int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *uid)
+{
+       char buf[sizeof(stringify_value(UINT32_MAX))];
+       uint32_t num;
+
+       assert(mode);
+       assert(uid);
+
+       if (mode_len > sizeof(buf) - 1) {
+               errno = ERANGE;
+               goto fail;
+       }
+       mem2strcpy(buf, mode, mode_len, sizeof(buf));
+
+       if (ul_strtou32(buf, &num, 8) != 0) {
+               errno = EINVAL;
+               goto fail;
+       }
+       if (num > 07777) {
+               errno = ERANGE;
+               goto fail;
+       }
+       *uid = (mode_t) num;
+
+       return 0;
+fail:
+       DBG(UTILS, ul_debug("failed to convert '%.*s' to mode [errno=%d]", (int) mode_len, mode, errno));
+       return -1;
+}
+
 int mnt_in_group(gid_t gid)
 {
        int rc = 0, n, i;
index 7465fcd0d99af8317f3741c8772a7749487813aa..5f0ddd05ba560497aa17dfdb5635ce8032623a77 100644 (file)
@@ -636,6 +636,12 @@ Allow to make a target directory (mountpoint) if it does not exist yet. The opti
 **X-mount.subdir=**__directory__::
 Allow mounting sub-directory from a filesystem instead of the root directory. For now, this feature is implemented by temporary filesystem root directory mount in unshared namespace and then bind the sub-directory to the final mount point and umount the root of the filesystem. The sub-directory mount shows up atomically for the rest of the system although it is implemented by multiple *mount*(2) syscalls. This feature is EXPERIMENTAL.
 
+*X-mount.owner*=_username_|_UID_, *X-mount.group*=_group_|_GID_::
+Set _mountpoint_'s ownership after mounting. Names resolved in the target mount namespace, see *-N*.
+
+*X-mount.mode*=_mode_::
+Set _mountpoint_'s mode after mounting.
+
 *nosymfollow*::
 Do not follow symlinks when resolving paths. Symlinks can still be created, and *readlink*(1), *readlink*(2), *realpath*(1), and *realpath*(3) all still work properly.
 
diff --git a/tests/expected/mount/set_ugid_mode b/tests/expected/mount/set_ugid_mode
new file mode 100644 (file)
index 0000000..3582111
--- /dev/null
@@ -0,0 +1 @@
+Success
diff --git a/tests/ts/mount/set_ugid_mode b/tests/ts/mount/set_ugid_mode
new file mode 100755 (executable)
index 0000000..810f81f
--- /dev/null
@@ -0,0 +1,65 @@
+#!/bin/bash
+# SPDX-License-Identifier: 0BSD
+
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="X-mount.{owner,group,mode}="
+
+. $TS_TOPDIR/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_MOUNT"
+ts_check_test_command "$TS_CMD_UMOUNT"
+
+ts_skip_nonroot
+ts_check_losetup
+ts_check_prog "mkfs.ext2"
+ts_check_prog "id"
+ts_check_prog "ls"
+
+
+do_one() {
+       expected="$1"; shift
+       what="$1"; shift
+       where="$1"; shift
+       $TS_CMD_MOUNT "$@" "$what" "$where" >> $TS_OUTPUT 2>> $TS_ERRLOG
+       read -r m _ o g _ < <(ls -nd "$where")
+       actual="$m $o $g"
+       [ "$actual" = "$expected" ] || echo "$*: $actual != $expected" >> $TS_ERRLOG
+       $TS_CMD_UMOUNT "$where" >> $TS_OUTPUT 2>> $TS_ERRLOG
+}
+
+ts_device_init
+
+mkfs.ext2 "$TS_LODEV" > /dev/null 2>&1  || ts_die "Cannot make ext2 on $TS_LODEV"
+ts_device_has "TYPE" "ext2" "$TS_LODEV" || ts_die "Cannot find ext2 on $TS_LODEV"
+
+user_1="$(id -un 1)"
+group_2="$(id -gn 2)"
+
+
+mkdir -p "$TS_MOUNTPOINT"
+
+do_one "drwxr-xr-x 0 0"     "$TS_LODEV" "$TS_MOUNTPOINT"
+do_one "drwxr-xr-x 1 0"     "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=$user_1"
+do_one "drwxr-xr-x 1 2"     "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.group=$group_2"
+do_one "d-w--wxr-T 132 2"   "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=132,X-mount.mode=1234"
+do_one "d-ws-w---x 132 123" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.mode=4321,X-mount.group=123"
+do_one "d-ws-w---x 1 321"   "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=$user_1,X-mount.group=321"
+
+
+> "$TS_MOUNTPOINT/bind"
+> "$TS_MOUNTPOINT/bindsrc"
+
+do_one "-rw-r--r-- 0 0"     "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind
+do_one "-rw-r--r-- 1 0"     "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=$user_1"
+do_one "-rw-r--r-- 1 2"     "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.group=$group_2"
+do_one "--w--wxr-T 132 2"   "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=132,X-mount.mode=1234"
+do_one "--ws-w---x 132 123" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.mode=4321,X-mount.group=123"
+do_one "--wx-w---x 1 321"   "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=$user_1,X-mount.group=321"
+
+
+rm -fd "$TS_MOUNTPOINT/bind"  "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT"
+
+ts_log "Success"
+ts_finalize