Hi, for years users are unhappy with too strict mount/umount rules for non-root users. The rules makes mount(8) useless for use-cases where root permissions are unnecessary. This patch changes the game. Please, review, complain, ... Karel >From 6497f2d99e9cabee3531e644ba4dcffd14532200 Mon Sep 17 00:00:00 2001 From: Karel Zak <kzak@xxxxxxxxxx> Date: Tue, 19 Nov 2019 14:58:20 +0100 Subject: [PATCH] mount: no exit on EPERM, continue without suid MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current libmount assumes that mount(8) and umount(8) are suid binaries. For this reason it implements internal rules which restrict what is allowed for non-root users. Unfortunately, it's out of reality for some use-cases where root permissions are no required. Nice example are fuse filesystems. So, the current situation is to call exit() always when mount, umount or libmount are unsure with non-root user rights. This patch removes the exit() call and replaces it with suid permissions drop, after that it continues as usually. It means after suid-drop all depend on kernel and no another security rule is used by libmount (simply because any rule is no more necessary). Example: old version: $ mount -t fuse.sshfs kzak@192.168.111.1:/home/kzak /home/kzak/mnt mount: only root can use "--types" option new version: $ mount -t fuse.sshfs kzak@192.168.111.1:/home/kzak /home/kzak/mnt kzak@192.168.111.1's password: $ findmnt /home/kzak/mnt TARGET SOURCE FSTYPE OPTIONS /home/kzak/mnt kzak@192.168.111.1:/home/kzak fuse.sshfs rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 $ umount /home/kzak/mnt $ echo $? 0 Note that fuse user umount is supported since v2.34 due to user_id= in kernel mount table. Signed-off-by: Karel Zak <kzak@xxxxxxxxxx> --- libmount/docs/libmount-sections.txt | 1 + libmount/src/context.c | 25 +++++++++++++++++ libmount/src/libmount.h.in | 1 + libmount/src/libmount.sym | 1 + sys-utils/mount.8 | 6 ++++ sys-utils/mount.c | 43 +++++++++++++++++------------ sys-utils/umount.8 | 19 +++++++++++++ sys-utils/umount.c | 37 +++++++++++++++---------- 8 files changed, 102 insertions(+), 31 deletions(-) diff --git a/libmount/docs/libmount-sections.txt b/libmount/docs/libmount-sections.txt index 990c0394f..0bba9f835 100644 --- a/libmount/docs/libmount-sections.txt +++ b/libmount/docs/libmount-sections.txt @@ -40,6 +40,7 @@ mnt_context_enable_rwonly_mount mnt_context_enable_sloppy mnt_context_enable_verbose mnt_context_forced_rdonly +mnt_context_force_unrestricted mnt_context_get_cache mnt_context_get_excode mnt_context_get_fs diff --git a/libmount/src/context.c b/libmount/src/context.c index 977842f7d..72a0c5a7b 100644 --- a/libmount/src/context.c +++ b/libmount/src/context.c @@ -426,6 +426,31 @@ int mnt_context_is_restricted(struct libmnt_context *cxt) return cxt->restricted; } +/** + * mnt_context_force_unrestricted: + * @cxt: mount context + * + * This function is DANGEROURS as it disables all security policies in libmount. + * Don't use if not sure. It removes "restricted" flag from the context, so + * libmount will use the current context as for root user. + * + * This function is designed for case you have no any suid permissions, so you + * can depend on kernel. + * + * Returns: 0 on success, negative number in case of error. + * + * Since: 2.35 + */ +int mnt_context_force_unrestricted(struct libmnt_context *cxt) +{ + if (mnt_context_is_restricted(cxt)) { + DBG(CXT, ul_debugobj(cxt, "force UNRESTRICTED")); + cxt->restricted = 0; + } + + return 0; +} + /** * mnt_context_set_optsmode * @cxt: mount context diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in index 19d4c5b53..ba54cf25d 100644 --- a/libmount/src/libmount.h.in +++ b/libmount/src/libmount.h.in @@ -705,6 +705,7 @@ extern void mnt_free_context(struct libmnt_context *cxt); extern int mnt_reset_context(struct libmnt_context *cxt); extern int mnt_context_is_restricted(struct libmnt_context *cxt) __ul_attribute__((nonnull)); +extern int mnt_context_force_unrestricted(struct libmnt_context *cxt); extern int mnt_context_init_helper(struct libmnt_context *cxt, int action, int flags); diff --git a/libmount/src/libmount.sym b/libmount/src/libmount.sym index b9a4c0d22..792d11753 100644 --- a/libmount/src/libmount.sym +++ b/libmount/src/libmount.sym @@ -352,6 +352,7 @@ MOUNT_2.34 { } MOUNT_2.33; MOUNT_2_35 { + mnt_context_force_unrestricted; mnt_context_get_target_prefix; mnt_context_set_target_prefix; } MOUNT_2.34; diff --git a/sys-utils/mount.8 b/sys-utils/mount.8 index 9d31d8245..a6231c7c1 100644 --- a/sys-utils/mount.8 +++ b/sys-utils/mount.8 @@ -315,6 +315,12 @@ program is executed. It's strongly recommended to use a valid mountpoint to specify filesystem, otherwise \fBmount\fR may fail. For example it's bad idea to use NFS or CIFS source on command line. .PP +Since version 2.35 \fBmount\fR command does not exit when user permissions are +inadequate by internal libmount security rules. It drops suid permissions +and continue as regular non-root user. It allows to support use-cases where +root permissions are not necessary (e.g. fuse filesystems, user namespaces, +etc). +.PP For more details, see .BR fstab (5). Only the user that mounted a filesystem can unmount it again. diff --git a/sys-utils/mount.c b/sys-utils/mount.c index 08da9e6a5..5842bc2ec 100644 --- a/sys-utils/mount.c +++ b/sys-utils/mount.c @@ -47,23 +47,24 @@ static int mk_exit_code(struct libmnt_context *cxt, int rc); -static void __attribute__((__noreturn__)) exit_non_root(const char *option) +static void suid_drop(struct libmnt_context *cxt) { const uid_t ruid = getuid(); const uid_t euid = geteuid(); - if (ruid == 0 && euid != 0) { - /* user is root, but setuid to non-root */ - if (option) - errx(MNT_EX_USAGE, _("only root can use \"--%s\" option " - "(effective UID is %u)"), - option, euid); - errx(MNT_EX_USAGE, _("only root can do that " - "(effective UID is %u)"), euid); + if (ruid != 0 && euid == 0) { + if (setgid(getgid()) < 0) + err(MNT_EX_FAIL, _("setgid() failed")); + + if (setuid(getuid()) < 0) + err(MNT_EX_FAIL, _("setuid() failed")); } - if (option) - errx(MNT_EX_USAGE, _("only root can use \"--%s\" option"), option); - errx(MNT_EX_USAGE, _("only root can do that")); + + /* be paranoid and check it, setuid(0) has to fail */ + if (ruid != 0 && setuid(0) == 0) + errx(MNT_EX_FAIL, _("drop permissions failed.")); + + mnt_context_force_unrestricted(cxt); } static void __attribute__((__noreturn__)) mount_print_version(void) @@ -672,7 +673,7 @@ int main(int argc, char **argv) !strchr("hlLUVvrist", c) && c != MOUNT_OPT_TARGET && c != MOUNT_OPT_SOURCE) - exit_non_root(option_to_longopt(c, longopts)); + suid_drop(cxt); err_exclusive_options(c, longopts, excl, excl_st); @@ -872,7 +873,7 @@ int main(int argc, char **argv) /* Non-root users are allowed to use -t to print_all(), but not to mount */ if (mnt_context_is_restricted(cxt) && types) - exit_non_root("types"); + suid_drop(cxt); if (oper && (types || all || mnt_context_get_source(cxt))) { warnx(_("bad usage")); @@ -905,7 +906,7 @@ int main(int argc, char **argv) if (mnt_context_is_restricted(cxt) && mnt_context_get_source(cxt) && mnt_context_get_target(cxt)) - exit_non_root(NULL); + suid_drop(cxt); } else if (argc == 1 && (!mnt_context_get_source(cxt) || !mnt_context_get_target(cxt))) { @@ -933,7 +934,7 @@ int main(int argc, char **argv) if (mnt_context_is_restricted(cxt) && mnt_context_get_source(cxt) && mnt_context_get_target(cxt)) - exit_non_root(NULL); + suid_drop(cxt); } else if (argc == 2 && !mnt_context_get_source(cxt) && !mnt_context_get_target(cxt)) { @@ -941,7 +942,7 @@ int main(int argc, char **argv) * D) mount <source> <target> */ if (mnt_context_is_restricted(cxt)) - exit_non_root(NULL); + suid_drop(cxt); mnt_context_set_source(cxt, argv[0]); mnt_context_set_target(cxt, argv[1]); @@ -963,6 +964,14 @@ int main(int argc, char **argv) mnt_context_set_optsmode(cxt, MNT_OMODE_NOTAB); rc = mnt_context_mount(cxt); + + if (rc == -EPERM + && mnt_context_is_restricted(cxt) + && !mnt_context_syscall_called(cxt)) { + /* Try it again without permissions */ + suid_drop(cxt); + rc = mnt_context_mount(cxt); + } rc = mk_exit_code(cxt, rc); if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt)) diff --git a/sys-utils/umount.8 b/sys-utils/umount.8 index f94d2f41f..9bec521e7 100644 --- a/sys-utils/umount.8 +++ b/sys-utils/umount.8 @@ -190,6 +190,25 @@ Display version information and exit. .TP .BR \-h , " \-\-help" Display help text and exit. +.SH "NON-SUPERUSER UMOUNTS" +Normally, only the superuser can umount filesystems. +However, when +.I fstab +contains the +.B user +option on a line, anybody can umount the corresponding filesystem. For more details see +.BR mount (8) +man page. +.PP +Since version 2.34 \fBumount\fR command allows to perform umount operation also +for fuse filesystems if kernel mount table contains user's ID. In this case fstab +user= mount option is not required. +.PP +Since version 2.35 \fBumount\fR command does not exit when user permissions are +inadequate by internal libmount security rules. It drops suid permissions +and continue as regular non-root user. It allows to support use-cases where +root permissions are not necessary (e.g. fuse filesystems, user namespaces, +etc). .SH "LOOP DEVICE" The .B umount diff --git a/sys-utils/umount.c b/sys-utils/umount.c index 397e0ebfc..74d87d671 100644 --- a/sys-utils/umount.c +++ b/sys-utils/umount.c @@ -112,24 +112,24 @@ static void __attribute__((__noreturn__)) usage(void) exit(MNT_EX_SUCCESS); } -static void __attribute__((__noreturn__)) exit_non_root(const char *option) +static void suid_drop(struct libmnt_context *cxt) { const uid_t ruid = getuid(); const uid_t euid = geteuid(); - if (ruid == 0 && euid != 0) { - /* user is root, but setuid to non-root */ - if (option) - errx(MNT_EX_USAGE, - _("only root can use \"--%s\" option " - "(effective UID is %u)"), - option, euid); - errx(MNT_EX_USAGE, _("only root can do that " - "(effective UID is %u)"), euid); + if (ruid != 0 && euid == 0) { + if (setgid(getgid()) < 0) + err(MNT_EX_FAIL, _("setgid() failed")); + + if (setuid(getuid()) < 0) + err(MNT_EX_FAIL, _("setuid() failed")); } - if (option) - errx(MNT_EX_USAGE, _("only root can use \"--%s\" option"), option); - errx(MNT_EX_USAGE, _("only root can do that")); + + /* be paranoid and check it, setuid(0) has to fail */ + if (ruid != 0 && setuid(0) == 0) + errx(MNT_EX_FAIL, _("drop permissions failed.")); + + mnt_context_force_unrestricted(cxt); } static void success_message(struct libmnt_context *cxt) @@ -220,6 +220,15 @@ static int umount_one(struct libmnt_context *cxt, const char *spec) err(MNT_EX_SYSERR, _("failed to set umount target")); rc = mnt_context_umount(cxt); + + if (rc == -EPERM + && mnt_context_is_restricted(cxt) + && !mnt_context_syscall_called(cxt)) { + /* Failed somewhere in libmount, drop perms and try it again */ + suid_drop(cxt); + rc = mnt_context_umount(cxt); + } + rc = mk_exit_code(cxt, rc); if (rc == MNT_EX_SUCCESS && mnt_context_is_verbose(cxt)) @@ -494,7 +503,7 @@ int main(int argc, char **argv) /* only few options are allowed for non-root users */ if (mnt_context_is_restricted(cxt) && !strchr("hdilqVv", c)) - exit_non_root(option_to_longopt(c, longopts)); + suid_drop(cxt); err_exclusive_options(c, longopts, excl, excl_st); -- 2.21.0