mount: no exit on EPERM, continue without suid

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@redhat.com>
This commit is contained in:
Karel Zak 2019-11-19 14:58:20 +01:00
parent 916a3f8d29
commit 6497f2d99e
8 changed files with 102 additions and 31 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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;

View File

@ -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.

View File

@ -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))

View File

@ -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

View File

@ -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);