2001-11-08 19:34:54 +08:00
|
|
|
/*
|
2007-12-12 22:25:40 +08:00
|
|
|
FUSE: Filesystem in Userspace
|
|
|
|
Copyright (C) 2001-2007 Miklos Szeredi <miklos@szeredi.hu>
|
2001-11-08 19:34:54 +08:00
|
|
|
|
2020-03-14 00:46:44 +08:00
|
|
|
This program can be distributed under the terms of the GNU GPLv2.
|
2007-12-12 22:25:40 +08:00
|
|
|
See the file COPYING.
|
2001-11-08 19:34:54 +08:00
|
|
|
*/
|
2001-11-12 02:20:17 +08:00
|
|
|
/* This program does the mounting and unmounting of FUSE filesystems */
|
|
|
|
|
2023-04-02 17:45:27 +08:00
|
|
|
#define _GNU_SOURCE /* for clone and strchrnul */
|
2023-01-28 04:54:48 +08:00
|
|
|
#include "fuse_config.h"
|
2007-04-25 23:52:39 +08:00
|
|
|
#include "mount_util.h"
|
2019-03-09 18:30:41 +08:00
|
|
|
|
2001-11-08 19:34:54 +08:00
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2005-01-15 17:42:01 +08:00
|
|
|
#include <ctype.h>
|
2001-11-08 19:34:54 +08:00
|
|
|
#include <unistd.h>
|
2005-07-21 15:59:37 +08:00
|
|
|
#include <getopt.h>
|
2001-11-08 19:34:54 +08:00
|
|
|
#include <errno.h>
|
|
|
|
#include <fcntl.h>
|
2001-11-08 22:56:53 +08:00
|
|
|
#include <pwd.h>
|
2013-08-26 17:57:16 +08:00
|
|
|
#include <paths.h>
|
2001-11-12 02:20:17 +08:00
|
|
|
#include <mntent.h>
|
2001-11-08 19:34:54 +08:00
|
|
|
#include <sys/wait.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/mount.h>
|
2001-11-09 22:49:18 +08:00
|
|
|
#include <sys/fsuid.h>
|
2002-10-25 19:40:14 +08:00
|
|
|
#include <sys/socket.h>
|
2004-12-12 19:45:24 +08:00
|
|
|
#include <sys/utsname.h>
|
2010-01-27 02:20:13 +08:00
|
|
|
#include <sched.h>
|
fusermount: don't feed "escaped commas" into mount options
The old code permits the following behavior:
$ _FUSE_COMMFD=10000 priv_strace -etrace=mount -s200 fusermount -o 'foobar=\,allow_other' mount
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "foobar=\\,allow_other,fd=3,rootmode=40000,user_id=1000,group_id=1000") = -1 EINVAL (Invalid argument)
However, backslashes do not have any special meaning for the kernel here.
As it happens, you can't abuse this because there is no FUSE mount option
that takes a string value that can contain backslashes; but this is very
brittle. Don't interpret "escape characters" in places where they don't
work.
2018-07-14 06:15:36 +08:00
|
|
|
#include <stdbool.h>
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
#include <sys/vfs.h>
|
2006-07-31 01:33:40 +08:00
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
#define FUSE_COMMFD_ENV "_FUSE_COMMFD"
|
2001-11-21 18:03:39 +08:00
|
|
|
|
2016-10-28 11:54:45 +08:00
|
|
|
#define FUSE_DEV "/dev/fuse"
|
2004-11-20 19:18:34 +08:00
|
|
|
|
2006-01-09 19:33:04 +08:00
|
|
|
#ifndef MS_DIRSYNC
|
|
|
|
#define MS_DIRSYNC 128
|
|
|
|
#endif
|
2010-01-27 02:20:13 +08:00
|
|
|
#ifndef MS_REC
|
|
|
|
#define MS_REC 16384
|
|
|
|
#endif
|
2010-04-26 23:29:08 +08:00
|
|
|
#ifndef MS_PRIVATE
|
|
|
|
#define MS_PRIVATE (1<<18)
|
2010-01-27 02:20:13 +08:00
|
|
|
#endif
|
2006-01-09 19:33:04 +08:00
|
|
|
|
2010-11-08 23:00:16 +08:00
|
|
|
#ifndef UMOUNT_DETACH
|
|
|
|
#define UMOUNT_DETACH 0x00000002 /* Just detach from the tree */
|
|
|
|
#endif
|
|
|
|
#ifndef UMOUNT_NOFOLLOW
|
|
|
|
#define UMOUNT_NOFOLLOW 0x00000008 /* Don't follow symlink on umount */
|
|
|
|
#endif
|
|
|
|
#ifndef UMOUNT_UNUSED
|
|
|
|
#define UMOUNT_UNUSED 0x80000000 /* Flag guaranteed to be unused */
|
|
|
|
#endif
|
|
|
|
|
2005-01-13 20:11:49 +08:00
|
|
|
static const char *progname;
|
|
|
|
|
|
|
|
static int user_allow_other = 0;
|
|
|
|
static int mount_max = 1000;
|
2001-11-08 22:56:53 +08:00
|
|
|
|
2011-03-12 14:59:14 +08:00
|
|
|
static int auto_unmount = 0;
|
|
|
|
|
2023-04-02 17:45:27 +08:00
|
|
|
#ifdef GETMNTENT_NEEDS_UNESCAPING
|
|
|
|
// Older versions of musl libc don't unescape entries in /etc/mtab
|
|
|
|
|
|
|
|
// unescapes octal sequences like \040 in-place
|
|
|
|
// That's ok, because unescaping can not extend the length of the string.
|
|
|
|
static void unescape(char *buf) {
|
|
|
|
char *src = buf;
|
|
|
|
char *dest = buf;
|
|
|
|
while (1) {
|
|
|
|
char *next_src = strchrnul(src, '\\');
|
|
|
|
int offset = next_src - src;
|
|
|
|
memmove(dest, src, offset);
|
|
|
|
src = next_src;
|
|
|
|
dest += offset;
|
|
|
|
|
|
|
|
if(*src == '\0') {
|
|
|
|
*dest = *src;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
src++;
|
|
|
|
|
|
|
|
if('0' <= src[0] && src[0] < '2' &&
|
|
|
|
'0' <= src[1] && src[1] < '8' &&
|
|
|
|
'0' <= src[2] && src[2] < '8') {
|
|
|
|
*dest++ = (src[0] - '0') << 6
|
|
|
|
| (src[1] - '0') << 3
|
|
|
|
| (src[2] - '0') << 0;
|
|
|
|
src += 3;
|
|
|
|
} else if (src[0] == '\\') {
|
|
|
|
*dest++ = '\\';
|
|
|
|
src += 1;
|
|
|
|
} else {
|
|
|
|
*dest++ = '\\';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct mntent *GETMNTENT(FILE *stream)
|
|
|
|
{
|
|
|
|
struct mntent *entp = getmntent(stream);
|
|
|
|
if(entp != NULL) {
|
|
|
|
unescape(entp->mnt_fsname);
|
|
|
|
unescape(entp->mnt_dir);
|
|
|
|
unescape(entp->mnt_type);
|
|
|
|
unescape(entp->mnt_opts);
|
|
|
|
}
|
|
|
|
return entp;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define GETMNTENT getmntent
|
|
|
|
#endif // GETMNTENT_NEEDS_UNESCAPING
|
|
|
|
|
|
|
|
|
2005-08-03 17:11:06 +08:00
|
|
|
static const char *get_user_name(void)
|
2001-11-08 22:56:53 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
struct passwd *pw = getpwuid(getuid());
|
|
|
|
if (pw != NULL && pw->pw_name != NULL)
|
|
|
|
return pw->pw_name;
|
|
|
|
else {
|
|
|
|
fprintf(stderr, "%s: could not determine username\n", progname);
|
|
|
|
return NULL;
|
|
|
|
}
|
2001-11-08 22:56:53 +08:00
|
|
|
}
|
|
|
|
|
2004-09-23 00:47:40 +08:00
|
|
|
static uid_t oldfsuid;
|
|
|
|
static gid_t oldfsgid;
|
|
|
|
|
2005-06-08 19:01:17 +08:00
|
|
|
static void drop_privs(void)
|
2004-09-23 00:47:40 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
if (getuid() != 0) {
|
|
|
|
oldfsuid = setfsuid(getuid());
|
|
|
|
oldfsgid = setfsgid(getgid());
|
|
|
|
}
|
2004-09-23 00:47:40 +08:00
|
|
|
}
|
|
|
|
|
2004-11-24 06:32:16 +08:00
|
|
|
static void restore_privs(void)
|
2004-09-23 00:47:40 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
if (getuid() != 0) {
|
|
|
|
setfsuid(oldfsuid);
|
|
|
|
setfsgid(oldfsgid);
|
|
|
|
}
|
2004-09-23 00:47:40 +08:00
|
|
|
}
|
2005-01-17 17:46:28 +08:00
|
|
|
|
2006-12-10 08:13:53 +08:00
|
|
|
#ifndef IGNORE_MTAB
|
2010-01-27 02:20:13 +08:00
|
|
|
/*
|
|
|
|
* Make sure that /etc/mtab is checked and updated atomically
|
|
|
|
*/
|
|
|
|
static int lock_umount(void)
|
|
|
|
{
|
|
|
|
const char *mtab_lock = _PATH_MOUNTED ".fuselock";
|
|
|
|
int mtablock;
|
|
|
|
int res;
|
|
|
|
struct stat mtab_stat;
|
|
|
|
|
|
|
|
/* /etc/mtab could be a symlink to /proc/mounts */
|
|
|
|
if (lstat(_PATH_MOUNTED, &mtab_stat) == 0 && S_ISLNK(mtab_stat.st_mode))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
mtablock = open(mtab_lock, O_RDWR | O_CREAT, 0600);
|
|
|
|
if (mtablock == -1) {
|
|
|
|
fprintf(stderr, "%s: unable to open fuse lock file: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
res = lockf(mtablock, F_LOCK, 0);
|
|
|
|
if (res < 0) {
|
|
|
|
fprintf(stderr, "%s: error getting lock: %s\n", progname,
|
|
|
|
strerror(errno));
|
|
|
|
close(mtablock);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return mtablock;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unlock_umount(int mtablock)
|
|
|
|
{
|
2011-03-07 21:40:59 +08:00
|
|
|
if (mtablock >= 0) {
|
2011-05-20 22:01:38 +08:00
|
|
|
int res;
|
|
|
|
|
|
|
|
res = lockf(mtablock, F_ULOCK, 0);
|
|
|
|
if (res < 0) {
|
|
|
|
fprintf(stderr, "%s: error releasing lock: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
}
|
2011-03-07 21:40:59 +08:00
|
|
|
close(mtablock);
|
|
|
|
}
|
2010-01-27 02:20:13 +08:00
|
|
|
}
|
|
|
|
|
2007-06-21 05:37:58 +08:00
|
|
|
static int add_mount(const char *source, const char *mnt, const char *type,
|
2007-12-12 22:25:40 +08:00
|
|
|
const char *opts)
|
2001-11-08 22:56:53 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
return fuse_mnt_add_mount(progname, source, mnt, type, opts);
|
2005-12-16 00:13:49 +08:00
|
|
|
}
|
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
static int may_unmount(const char *mnt, int quiet)
|
2004-11-24 06:32:16 +08:00
|
|
|
{
|
2010-01-27 02:20:13 +08:00
|
|
|
struct mntent *entp;
|
|
|
|
FILE *fp;
|
|
|
|
const char *user = NULL;
|
|
|
|
char uidstr[32];
|
|
|
|
unsigned uidlen = 0;
|
|
|
|
int found;
|
|
|
|
const char *mtab = _PATH_MOUNTED;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
user = get_user_name();
|
|
|
|
if (user == NULL)
|
|
|
|
return -1;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
fp = setmntent(mtab, "r");
|
|
|
|
if (fp == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
|
|
|
|
strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uidlen = sprintf(uidstr, "%u", getuid());
|
|
|
|
|
|
|
|
found = 0;
|
2023-04-02 17:45:27 +08:00
|
|
|
while ((entp = GETMNTENT(fp)) != NULL) {
|
2010-01-27 02:20:13 +08:00
|
|
|
if (!found && strcmp(entp->mnt_dir, mnt) == 0 &&
|
|
|
|
(strcmp(entp->mnt_type, "fuse") == 0 ||
|
|
|
|
strcmp(entp->mnt_type, "fuseblk") == 0 ||
|
|
|
|
strncmp(entp->mnt_type, "fuse.", 5) == 0 ||
|
|
|
|
strncmp(entp->mnt_type, "fuseblk.", 8) == 0)) {
|
|
|
|
char *p = strstr(entp->mnt_opts, "user=");
|
|
|
|
if (p &&
|
|
|
|
(p == entp->mnt_opts || *(p-1) == ',') &&
|
|
|
|
strcmp(p + 5, user) == 0) {
|
|
|
|
found = 1;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* /etc/mtab is a link pointing to
|
|
|
|
/proc/mounts: */
|
|
|
|
else if ((p =
|
|
|
|
strstr(entp->mnt_opts, "user_id=")) &&
|
|
|
|
(p == entp->mnt_opts ||
|
|
|
|
*(p-1) == ',') &&
|
|
|
|
strncmp(p + 8, uidstr, uidlen) == 0 &&
|
|
|
|
(*(p+8+uidlen) == ',' ||
|
|
|
|
*(p+8+uidlen) == '\0')) {
|
|
|
|
found = 1;
|
|
|
|
break;
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
}
|
2010-01-27 02:20:13 +08:00
|
|
|
}
|
|
|
|
endmntent(fp);
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
if (!found) {
|
|
|
|
if (!quiet)
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: entry for %s not found in %s\n",
|
|
|
|
progname, mnt, mtab);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2019-06-15 18:16:45 +08:00
|
|
|
#endif
|
2010-01-27 02:20:13 +08:00
|
|
|
|
|
|
|
/*
|
2016-11-29 13:33:14 +08:00
|
|
|
* Check whether the file specified in "fusermount3 -u" is really a
|
2010-01-27 02:20:13 +08:00
|
|
|
* mountpoint and not a symlink. This is necessary otherwise the user
|
|
|
|
* could move the mountpoint away and replace it with a symlink
|
2016-11-29 13:33:14 +08:00
|
|
|
* pointing to an arbitrary mount, thereby tricking fusermount3 into
|
2010-01-27 02:20:13 +08:00
|
|
|
* unmounting that (umount(2) will follow symlinks).
|
|
|
|
*
|
|
|
|
* This is the child process running in a separate mount namespace, so
|
|
|
|
* we don't mess with the global namespace and if the process is
|
|
|
|
* killed for any reason, mounts are automatically cleaned up.
|
|
|
|
*
|
|
|
|
* First make sure nothing is propagated back into the parent
|
2010-04-26 23:29:08 +08:00
|
|
|
* namespace by marking all mounts "private".
|
2010-01-27 02:20:13 +08:00
|
|
|
*
|
|
|
|
* Then bind mount parent onto a stable base where the user can't move
|
2010-04-26 23:29:08 +08:00
|
|
|
* it around.
|
2010-01-27 02:20:13 +08:00
|
|
|
*
|
|
|
|
* Finally check /proc/mounts for an entry matching the requested
|
|
|
|
* mountpoint. If it's found then we are OK, and the user can't move
|
2010-04-26 23:29:08 +08:00
|
|
|
* it around within the parent directory as rename() will return
|
|
|
|
* EBUSY. Be careful to ignore any mounts that existed before the
|
|
|
|
* bind.
|
2010-01-27 02:20:13 +08:00
|
|
|
*/
|
|
|
|
static int check_is_mount_child(void *p)
|
|
|
|
{
|
|
|
|
const char **a = p;
|
|
|
|
const char *last = a[0];
|
|
|
|
const char *mnt = a[1];
|
2018-10-17 07:23:07 +08:00
|
|
|
const char *type = a[2];
|
2010-01-27 02:20:13 +08:00
|
|
|
int res;
|
|
|
|
const char *procmounts = "/proc/mounts";
|
|
|
|
int found;
|
|
|
|
FILE *fp;
|
|
|
|
struct mntent *entp;
|
2010-04-26 23:29:08 +08:00
|
|
|
int count;
|
2010-01-27 02:20:13 +08:00
|
|
|
|
2010-04-26 23:29:08 +08:00
|
|
|
res = mount("", "/", "", MS_PRIVATE | MS_REC, NULL);
|
2010-01-27 02:20:13 +08:00
|
|
|
if (res == -1) {
|
2010-04-26 23:29:08 +08:00
|
|
|
fprintf(stderr, "%s: failed to mark mounts private: %s\n",
|
2010-01-27 02:20:13 +08:00
|
|
|
progname, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-04-26 23:29:08 +08:00
|
|
|
fp = setmntent(procmounts, "r");
|
|
|
|
if (fp == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n", progname,
|
|
|
|
procmounts, strerror(errno));
|
2010-01-27 02:20:13 +08:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-04-26 23:29:08 +08:00
|
|
|
count = 0;
|
2023-04-02 17:45:27 +08:00
|
|
|
while (GETMNTENT(fp) != NULL)
|
2010-04-26 23:29:08 +08:00
|
|
|
count++;
|
|
|
|
endmntent(fp);
|
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
fp = setmntent(procmounts, "r");
|
|
|
|
if (fp == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n", progname,
|
|
|
|
procmounts, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-04-26 23:29:08 +08:00
|
|
|
res = mount(".", "/", "", MS_BIND | MS_REC, NULL);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to bind parent to /: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
found = 0;
|
2023-04-02 17:45:27 +08:00
|
|
|
while ((entp = GETMNTENT(fp)) != NULL) {
|
2010-04-26 23:29:08 +08:00
|
|
|
if (count > 0) {
|
|
|
|
count--;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (entp->mnt_dir[0] == '/' &&
|
2018-10-17 07:23:07 +08:00
|
|
|
strcmp(entp->mnt_dir + 1, last) == 0 &&
|
|
|
|
(!type || strcmp(entp->mnt_type, type) == 0)) {
|
2010-01-27 02:20:13 +08:00
|
|
|
found = 1;
|
|
|
|
break;
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
}
|
2010-01-27 02:20:13 +08:00
|
|
|
endmntent(fp);
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
fprintf(stderr, "%s: %s not mounted\n", progname, mnt);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static pid_t clone_newns(void *a)
|
|
|
|
{
|
2010-02-18 19:05:13 +08:00
|
|
|
char buf[131072];
|
|
|
|
char *stack = buf + (sizeof(buf) / 2 - ((size_t) buf & 15));
|
2010-01-27 02:20:13 +08:00
|
|
|
|
|
|
|
#ifdef __ia64__
|
|
|
|
extern int __clone2(int (*fn)(void *),
|
|
|
|
void *child_stack_base, size_t stack_size,
|
|
|
|
int flags, void *arg, pid_t *ptid,
|
|
|
|
void *tls, pid_t *ctid);
|
|
|
|
|
2010-02-18 19:05:13 +08:00
|
|
|
return __clone2(check_is_mount_child, stack, sizeof(buf) / 2,
|
|
|
|
CLONE_NEWNS, a, NULL, NULL, NULL);
|
2010-01-27 02:20:13 +08:00
|
|
|
#else
|
|
|
|
return clone(check_is_mount_child, stack, CLONE_NEWNS, a);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
static int check_is_mount(const char *last, const char *mnt, const char *type)
|
2010-01-27 02:20:13 +08:00
|
|
|
{
|
|
|
|
pid_t pid, p;
|
|
|
|
int status;
|
2018-10-17 07:23:07 +08:00
|
|
|
const char *a[3] = { last, mnt, type };
|
2010-01-27 02:20:13 +08:00
|
|
|
|
|
|
|
pid = clone_newns((void *) a);
|
|
|
|
if (pid == (pid_t) -1) {
|
|
|
|
fprintf(stderr, "%s: failed to clone namespace: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
p = waitpid(pid, &status, __WCLONE);
|
|
|
|
if (p == (pid_t) -1) {
|
|
|
|
fprintf(stderr, "%s: waitpid failed: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!WIFEXITED(status)) {
|
|
|
|
fprintf(stderr, "%s: child terminated abnormally (status %i)\n",
|
|
|
|
progname, status);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (WEXITSTATUS(status) != 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-11-09 00:53:47 +08:00
|
|
|
static int chdir_to_parent(char *copy, const char **lastp)
|
2010-01-27 02:20:13 +08:00
|
|
|
{
|
|
|
|
char *tmp;
|
|
|
|
const char *parent;
|
|
|
|
char buf[65536];
|
|
|
|
int res;
|
|
|
|
|
|
|
|
tmp = strrchr(copy, '/');
|
|
|
|
if (tmp == NULL || tmp[1] == '\0') {
|
|
|
|
fprintf(stderr, "%s: internal error: invalid abs path: <%s>\n",
|
|
|
|
progname, copy);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (tmp != copy) {
|
|
|
|
*tmp = '\0';
|
|
|
|
parent = copy;
|
|
|
|
*lastp = tmp + 1;
|
|
|
|
} else if (tmp[1] != '\0') {
|
|
|
|
*lastp = tmp + 1;
|
|
|
|
parent = "/";
|
|
|
|
} else {
|
|
|
|
*lastp = ".";
|
|
|
|
parent = "/";
|
|
|
|
}
|
|
|
|
|
|
|
|
res = chdir(parent);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to chdir to %s: %s\n",
|
|
|
|
progname, parent, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (getcwd(buf, sizeof(buf)) == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to obtain current directory: %s\n",
|
|
|
|
progname, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (strcmp(buf, parent) != 0) {
|
|
|
|
fprintf(stderr, "%s: mountpoint moved (%s -> %s)\n", progname,
|
|
|
|
parent, buf);
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-06-15 18:16:45 +08:00
|
|
|
#ifndef IGNORE_MTAB
|
2010-11-08 23:00:16 +08:00
|
|
|
static int unmount_fuse_locked(const char *mnt, int quiet, int lazy)
|
|
|
|
{
|
|
|
|
int res;
|
2011-01-31 23:22:41 +08:00
|
|
|
char *copy;
|
|
|
|
const char *last;
|
2020-12-27 19:17:46 +08:00
|
|
|
int umount_flags = (lazy ? UMOUNT_DETACH : 0) | UMOUNT_NOFOLLOW;
|
2010-11-08 23:00:16 +08:00
|
|
|
|
|
|
|
if (getuid() != 0) {
|
|
|
|
res = may_unmount(mnt, quiet);
|
|
|
|
if (res == -1)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-01-31 23:22:41 +08:00
|
|
|
copy = strdup(mnt);
|
|
|
|
if (copy == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2019-03-09 05:26:31 +08:00
|
|
|
drop_privs();
|
2011-01-31 23:22:41 +08:00
|
|
|
res = chdir_to_parent(copy, &last);
|
2019-03-09 05:26:31 +08:00
|
|
|
restore_privs();
|
2011-01-31 23:22:41 +08:00
|
|
|
if (res == -1)
|
|
|
|
goto out;
|
2010-11-08 23:00:16 +08:00
|
|
|
|
2011-01-31 23:22:41 +08:00
|
|
|
res = umount2(last, umount_flags);
|
|
|
|
if (res == -1 && !quiet) {
|
|
|
|
fprintf(stderr, "%s: failed to unmount %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2018-07-22 05:14:06 +08:00
|
|
|
free(copy);
|
2011-01-31 23:22:41 +08:00
|
|
|
if (res == -1)
|
|
|
|
return -1;
|
|
|
|
|
2011-05-20 22:01:38 +08:00
|
|
|
res = chdir("/");
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-01-31 23:22:41 +08:00
|
|
|
return fuse_mnt_remove_mount(progname, mnt);
|
2010-11-08 23:00:16 +08:00
|
|
|
}
|
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
static int unmount_fuse(const char *mnt, int quiet, int lazy)
|
|
|
|
{
|
|
|
|
int res;
|
|
|
|
int mtablock = lock_umount();
|
|
|
|
|
|
|
|
res = unmount_fuse_locked(mnt, quiet, lazy);
|
|
|
|
unlock_umount(mtablock);
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2010-01-27 02:20:13 +08:00
|
|
|
return res;
|
2004-11-24 06:32:16 +08:00
|
|
|
}
|
|
|
|
|
2005-08-03 17:11:06 +08:00
|
|
|
static int count_fuse_fs(void)
|
2005-01-13 20:11:49 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
struct mntent *entp;
|
|
|
|
int count = 0;
|
|
|
|
const char *mtab = _PATH_MOUNTED;
|
|
|
|
FILE *fp = setmntent(mtab, "r");
|
|
|
|
if (fp == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n", progname, mtab,
|
|
|
|
strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
2023-04-02 17:45:27 +08:00
|
|
|
while ((entp = GETMNTENT(fp)) != NULL) {
|
2007-12-12 22:25:40 +08:00
|
|
|
if (strcmp(entp->mnt_type, "fuse") == 0 ||
|
|
|
|
strncmp(entp->mnt_type, "fuse.", 5) == 0)
|
|
|
|
count ++;
|
|
|
|
}
|
|
|
|
endmntent(fp);
|
|
|
|
return count;
|
2005-01-13 20:11:49 +08:00
|
|
|
}
|
2004-11-24 06:32:16 +08:00
|
|
|
|
2005-02-02 19:14:04 +08:00
|
|
|
|
2005-09-28 22:50:49 +08:00
|
|
|
#else /* IGNORE_MTAB */
|
2018-05-08 13:57:00 +08:00
|
|
|
static int count_fuse_fs(void)
|
2005-01-17 17:46:28 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
return 0;
|
2005-01-17 17:46:28 +08:00
|
|
|
}
|
|
|
|
|
2007-06-21 05:37:58 +08:00
|
|
|
static int add_mount(const char *source, const char *mnt, const char *type,
|
2007-12-12 22:25:40 +08:00
|
|
|
const char *opts)
|
2005-01-17 17:46:28 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
(void) source;
|
|
|
|
(void) mnt;
|
|
|
|
(void) type;
|
|
|
|
(void) opts;
|
|
|
|
return 0;
|
2005-01-17 17:46:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int unmount_fuse(const char *mnt, int quiet, int lazy)
|
|
|
|
{
|
2018-05-08 13:57:00 +08:00
|
|
|
(void) quiet;
|
2010-01-27 02:20:13 +08:00
|
|
|
return fuse_mnt_umount(progname, mnt, mnt, lazy);
|
2005-01-17 17:46:28 +08:00
|
|
|
}
|
2005-09-28 22:50:49 +08:00
|
|
|
#endif /* IGNORE_MTAB */
|
2004-11-24 06:32:16 +08:00
|
|
|
|
2005-01-15 17:42:01 +08:00
|
|
|
static void strip_line(char *line)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
char *s = strchr(line, '#');
|
|
|
|
if (s != NULL)
|
|
|
|
s[0] = '\0';
|
|
|
|
for (s = line + strlen(line) - 1;
|
|
|
|
s >= line && isspace((unsigned char) *s); s--);
|
|
|
|
s[1] = '\0';
|
|
|
|
for (s = line; isspace((unsigned char) *s); s++);
|
|
|
|
if (s != line)
|
|
|
|
memmove(line, s, strlen(s)+1);
|
2005-01-15 17:42:01 +08:00
|
|
|
}
|
|
|
|
|
2005-01-22 02:28:05 +08:00
|
|
|
static void parse_line(char *line, int linenum)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int tmp;
|
|
|
|
if (strcmp(line, "user_allow_other") == 0)
|
|
|
|
user_allow_other = 1;
|
|
|
|
else if (sscanf(line, "mount_max = %i", &tmp) == 1)
|
|
|
|
mount_max = tmp;
|
|
|
|
else if(line[0])
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: unknown parameter in %s at line %i: '%s'\n",
|
|
|
|
progname, FUSE_CONF, linenum, line);
|
2005-01-22 02:28:05 +08:00
|
|
|
}
|
|
|
|
|
2005-01-13 20:11:49 +08:00
|
|
|
static void read_conf(void)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
FILE *fp = fopen(FUSE_CONF, "r");
|
|
|
|
if (fp != NULL) {
|
|
|
|
int linenum = 1;
|
|
|
|
char line[256];
|
|
|
|
int isnewline = 1;
|
|
|
|
while (fgets(line, sizeof(line), fp) != NULL) {
|
|
|
|
if (isnewline) {
|
|
|
|
if (line[strlen(line)-1] == '\n') {
|
|
|
|
strip_line(line);
|
|
|
|
parse_line(line, linenum);
|
|
|
|
} else {
|
|
|
|
isnewline = 0;
|
|
|
|
}
|
2008-04-09 21:23:36 +08:00
|
|
|
} else if(line[strlen(line)-1] == '\n') {
|
|
|
|
fprintf(stderr, "%s: reading %s: line %i too long\n", progname, FUSE_CONF, linenum);
|
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
isnewline = 1;
|
2008-04-09 21:23:36 +08:00
|
|
|
}
|
2007-12-12 22:25:40 +08:00
|
|
|
if (isnewline)
|
|
|
|
linenum ++;
|
|
|
|
}
|
2008-04-09 21:23:36 +08:00
|
|
|
if (!isnewline) {
|
|
|
|
fprintf(stderr, "%s: reading %s: missing newline at end of file\n", progname, FUSE_CONF);
|
|
|
|
|
|
|
|
}
|
2018-07-14 06:50:50 +08:00
|
|
|
if (ferror(fp)) {
|
|
|
|
fprintf(stderr, "%s: reading %s: read failed\n", progname, FUSE_CONF);
|
|
|
|
exit(1);
|
|
|
|
}
|
2007-12-12 22:25:40 +08:00
|
|
|
fclose(fp);
|
|
|
|
} else if (errno != ENOENT) {
|
2018-07-14 06:50:50 +08:00
|
|
|
bool fatal = (errno != EACCES && errno != ELOOP &&
|
|
|
|
errno != ENAMETOOLONG && errno != ENOTDIR &&
|
|
|
|
errno != EOVERFLOW);
|
2007-12-12 22:25:40 +08:00
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n",
|
|
|
|
progname, FUSE_CONF, strerror(errno));
|
2018-07-14 06:50:50 +08:00
|
|
|
if (fatal)
|
|
|
|
exit(1);
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
2005-01-13 20:11:49 +08:00
|
|
|
}
|
|
|
|
|
2004-07-24 01:16:29 +08:00
|
|
|
static int begins_with(const char *s, const char *beg)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
if (strncmp(s, beg, strlen(beg)) == 0)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
2004-07-24 01:16:29 +08:00
|
|
|
}
|
|
|
|
|
2004-11-20 19:18:34 +08:00
|
|
|
struct mount_flags {
|
2007-12-12 22:25:40 +08:00
|
|
|
const char *opt;
|
|
|
|
unsigned long flag;
|
|
|
|
int on;
|
|
|
|
int safe;
|
2004-11-20 19:18:34 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static struct mount_flags mount_flags[] = {
|
2007-12-12 22:25:40 +08:00
|
|
|
{"rw", MS_RDONLY, 0, 1},
|
|
|
|
{"ro", MS_RDONLY, 1, 1},
|
|
|
|
{"suid", MS_NOSUID, 0, 0},
|
|
|
|
{"nosuid", MS_NOSUID, 1, 1},
|
|
|
|
{"dev", MS_NODEV, 0, 0},
|
|
|
|
{"nodev", MS_NODEV, 1, 1},
|
|
|
|
{"exec", MS_NOEXEC, 0, 1},
|
|
|
|
{"noexec", MS_NOEXEC, 1, 1},
|
|
|
|
{"async", MS_SYNCHRONOUS, 0, 1},
|
|
|
|
{"sync", MS_SYNCHRONOUS, 1, 1},
|
|
|
|
{"atime", MS_NOATIME, 0, 1},
|
|
|
|
{"noatime", MS_NOATIME, 1, 1},
|
2023-03-20 05:12:36 +08:00
|
|
|
{"diratime", MS_NODIRATIME, 0, 1},
|
|
|
|
{"nodiratime", MS_NODIRATIME, 1, 1},
|
|
|
|
{"lazytime", MS_LAZYTIME, 1, 1},
|
|
|
|
{"nolazytime", MS_LAZYTIME, 0, 1},
|
|
|
|
{"relatime", MS_RELATIME, 1, 1},
|
|
|
|
{"norelatime", MS_RELATIME, 0, 1},
|
|
|
|
{"strictatime", MS_STRICTATIME, 1, 1},
|
|
|
|
{"nostrictatime", MS_STRICTATIME, 0, 1},
|
2007-12-12 22:25:40 +08:00
|
|
|
{"dirsync", MS_DIRSYNC, 1, 1},
|
|
|
|
{NULL, 0, 0, 0}
|
2004-11-20 19:18:34 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int find_mount_flag(const char *s, unsigned len, int *on, int *flag)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; mount_flags[i].opt != NULL; i++) {
|
|
|
|
const char *opt = mount_flags[i].opt;
|
|
|
|
if (strlen(opt) == len && strncmp(opt, s, len) == 0) {
|
|
|
|
*on = mount_flags[i].on;
|
|
|
|
*flag = mount_flags[i].flag;
|
|
|
|
if (!mount_flags[i].safe && getuid() != 0) {
|
|
|
|
*flag = 0;
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: unsafe option %s ignored\n",
|
|
|
|
progname, opt);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int add_option(char **optsp, const char *opt, unsigned expand)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
char *newopts;
|
|
|
|
if (*optsp == NULL)
|
|
|
|
newopts = strdup(opt);
|
|
|
|
else {
|
|
|
|
unsigned oldsize = strlen(*optsp);
|
|
|
|
unsigned newsize = oldsize + 1 + strlen(opt) + expand + 1;
|
|
|
|
newopts = (char *) realloc(*optsp, newsize);
|
|
|
|
if (newopts)
|
|
|
|
sprintf(newopts + oldsize, ",%s", opt);
|
|
|
|
}
|
|
|
|
if (newopts == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
*optsp = newopts;
|
|
|
|
return 0;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int get_mnt_opts(int flags, char *opts, char **mnt_optsp)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int i;
|
|
|
|
int l;
|
|
|
|
|
|
|
|
if (!(flags & MS_RDONLY) && add_option(mnt_optsp, "rw", 0) == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (i = 0; mount_flags[i].opt != NULL; i++) {
|
|
|
|
if (mount_flags[i].on && (flags & mount_flags[i].flag) &&
|
|
|
|
add_option(mnt_optsp, mount_flags[i].opt, 0) == -1)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (add_option(mnt_optsp, opts, 0) == -1)
|
|
|
|
return -1;
|
|
|
|
/* remove comma from end of opts*/
|
|
|
|
l = strlen(*mnt_optsp);
|
|
|
|
if ((*mnt_optsp)[l-1] == ',')
|
|
|
|
(*mnt_optsp)[l-1] = '\0';
|
|
|
|
if (getuid() != 0) {
|
|
|
|
const char *user = get_user_name();
|
|
|
|
if (user == NULL)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (add_option(mnt_optsp, "user=", strlen(user)) == -1)
|
|
|
|
return -1;
|
|
|
|
strcat(*mnt_optsp, user);
|
|
|
|
}
|
|
|
|
return 0;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
2005-01-13 20:11:49 +08:00
|
|
|
static int opt_eq(const char *s, unsigned len, const char *opt)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
if(strlen(opt) == len && strncmp(s, opt, len) == 0)
|
|
|
|
return 1;
|
|
|
|
else
|
|
|
|
return 0;
|
2005-01-13 20:11:49 +08:00
|
|
|
}
|
|
|
|
|
2007-06-21 05:37:58 +08:00
|
|
|
static int get_string_opt(const char *s, unsigned len, const char *opt,
|
2007-12-12 22:25:40 +08:00
|
|
|
char **val)
|
2006-11-30 00:01:23 +08:00
|
|
|
{
|
2010-09-28 16:13:24 +08:00
|
|
|
int i;
|
2007-12-12 22:25:40 +08:00
|
|
|
unsigned opt_len = strlen(opt);
|
2010-09-28 16:13:24 +08:00
|
|
|
char *d;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
if (*val)
|
|
|
|
free(*val);
|
|
|
|
*val = (char *) malloc(len - opt_len + 1);
|
|
|
|
if (!*val) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-09-28 16:13:24 +08:00
|
|
|
d = *val;
|
|
|
|
s += opt_len;
|
|
|
|
len -= opt_len;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
|
|
if (s[i] == '\\' && i + 1 < len)
|
|
|
|
i++;
|
|
|
|
*d++ = s[i];
|
|
|
|
}
|
|
|
|
*d = '\0';
|
2007-12-12 22:25:40 +08:00
|
|
|
return 1;
|
2006-11-30 00:01:23 +08:00
|
|
|
}
|
|
|
|
|
fusermount: prevent silent truncation of mount options
Currently, in the kernel, copy_mount_options() copies in one page of
userspace memory (or less if some of that memory area is not mapped).
do_mount() then writes a null byte to the last byte of the copied page.
This means that mount option strings longer than PAGE_SIZE-1 bytes get
truncated silently.
Therefore, this can happen:
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0
user@d9-ut:~$ fusermount -u mount
I'm not aware of any context in which this is actually exploitable - you'd
still need the UIDs to fit, and you can't do it if the three GIDs of the
process don't match (in the case of a typical setgid binary), but it does
look like something that should be fixed.
I also plan to try to get this fixed on the kernel side.
2018-07-14 05:51:17 +08:00
|
|
|
/* The kernel silently truncates the "data" argument to PAGE_SIZE-1 characters.
|
|
|
|
* This can be dangerous if it e.g. truncates the option "group_id=1000" to
|
|
|
|
* "group_id=1".
|
|
|
|
* This wrapper detects this case and bails out with an error.
|
|
|
|
*/
|
|
|
|
static int mount_notrunc(const char *source, const char *target,
|
|
|
|
const char *filesystemtype, unsigned long mountflags,
|
|
|
|
const char *data) {
|
|
|
|
if (strlen(data) > sysconf(_SC_PAGESIZE) - 1) {
|
|
|
|
fprintf(stderr, "%s: mount options too long\n", progname);
|
|
|
|
errno = EINVAL;
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return mount(source, target, filesystemtype, mountflags, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
static int do_mount(const char *mnt, const char **typep, mode_t rootmode,
|
2007-12-12 22:25:40 +08:00
|
|
|
int fd, const char *opts, const char *dev, char **sourcep,
|
2016-10-08 11:57:53 +08:00
|
|
|
char **mnt_optsp)
|
2001-11-08 19:34:54 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int res;
|
|
|
|
int flags = MS_NOSUID | MS_NODEV;
|
|
|
|
char *optbuf;
|
|
|
|
char *mnt_opts = NULL;
|
|
|
|
const char *s;
|
|
|
|
char *d;
|
|
|
|
char *fsname = NULL;
|
|
|
|
char *subtype = NULL;
|
|
|
|
char *source = NULL;
|
|
|
|
char *type = NULL;
|
|
|
|
int blkdev = 0;
|
|
|
|
|
|
|
|
optbuf = (char *) malloc(strlen(opts) + 128);
|
|
|
|
if (!optbuf) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (s = opts, d = optbuf; *s;) {
|
|
|
|
unsigned len;
|
|
|
|
const char *fsname_str = "fsname=";
|
|
|
|
const char *subtype_str = "subtype=";
|
fusermount: don't feed "escaped commas" into mount options
The old code permits the following behavior:
$ _FUSE_COMMFD=10000 priv_strace -etrace=mount -s200 fusermount -o 'foobar=\,allow_other' mount
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "foobar=\\,allow_other,fd=3,rootmode=40000,user_id=1000,group_id=1000") = -1 EINVAL (Invalid argument)
However, backslashes do not have any special meaning for the kernel here.
As it happens, you can't abuse this because there is no FUSE mount option
that takes a string value that can contain backslashes; but this is very
brittle. Don't interpret "escape characters" in places where they don't
work.
2018-07-14 06:15:36 +08:00
|
|
|
bool escape_ok = begins_with(s, fsname_str) ||
|
|
|
|
begins_with(s, subtype_str);
|
2010-09-28 16:13:24 +08:00
|
|
|
for (len = 0; s[len]; len++) {
|
fusermount: don't feed "escaped commas" into mount options
The old code permits the following behavior:
$ _FUSE_COMMFD=10000 priv_strace -etrace=mount -s200 fusermount -o 'foobar=\,allow_other' mount
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "foobar=\\,allow_other,fd=3,rootmode=40000,user_id=1000,group_id=1000") = -1 EINVAL (Invalid argument)
However, backslashes do not have any special meaning for the kernel here.
As it happens, you can't abuse this because there is no FUSE mount option
that takes a string value that can contain backslashes; but this is very
brittle. Don't interpret "escape characters" in places where they don't
work.
2018-07-14 06:15:36 +08:00
|
|
|
if (escape_ok && s[len] == '\\' && s[len + 1])
|
2010-09-28 16:13:24 +08:00
|
|
|
len++;
|
|
|
|
else if (s[len] == ',')
|
|
|
|
break;
|
|
|
|
}
|
2007-12-12 22:25:40 +08:00
|
|
|
if (begins_with(s, fsname_str)) {
|
|
|
|
if (!get_string_opt(s, len, fsname_str, &fsname))
|
|
|
|
goto err;
|
|
|
|
} else if (begins_with(s, subtype_str)) {
|
|
|
|
if (!get_string_opt(s, len, subtype_str, &subtype))
|
|
|
|
goto err;
|
|
|
|
} else if (opt_eq(s, len, "blkdev")) {
|
|
|
|
if (getuid() != 0) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: option blkdev is privileged\n",
|
|
|
|
progname);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
blkdev = 1;
|
2011-03-12 14:59:14 +08:00
|
|
|
} else if (opt_eq(s, len, "auto_unmount")) {
|
|
|
|
auto_unmount = 1;
|
2021-01-29 05:51:10 +08:00
|
|
|
} else if (!opt_eq(s, len, "nonempty") &&
|
|
|
|
!begins_with(s, "fd=") &&
|
2007-12-12 22:25:40 +08:00
|
|
|
!begins_with(s, "rootmode=") &&
|
|
|
|
!begins_with(s, "user_id=") &&
|
|
|
|
!begins_with(s, "group_id=")) {
|
|
|
|
int on;
|
|
|
|
int flag;
|
|
|
|
int skip_option = 0;
|
|
|
|
if (opt_eq(s, len, "large_read")) {
|
|
|
|
struct utsname utsname;
|
|
|
|
unsigned kmaj, kmin;
|
|
|
|
res = uname(&utsname);
|
|
|
|
if (res == 0 &&
|
|
|
|
sscanf(utsname.release, "%u.%u",
|
|
|
|
&kmaj, &kmin) == 2 &&
|
|
|
|
(kmaj > 2 || (kmaj == 2 && kmin > 4))) {
|
|
|
|
fprintf(stderr, "%s: note: 'large_read' mount option is deprecated for %i.%i kernels\n", progname, kmaj, kmin);
|
|
|
|
skip_option = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (getuid() != 0 && !user_allow_other &&
|
|
|
|
(opt_eq(s, len, "allow_other") ||
|
|
|
|
opt_eq(s, len, "allow_root"))) {
|
2016-11-29 13:33:14 +08:00
|
|
|
fprintf(stderr, "%s: option %.*s only allowed if 'user_allow_other' is set in %s\n", progname, len, s, FUSE_CONF);
|
2007-12-12 22:25:40 +08:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
if (!skip_option) {
|
|
|
|
if (find_mount_flag(s, len, &on, &flag)) {
|
|
|
|
if (on)
|
|
|
|
flags |= flag;
|
|
|
|
else
|
|
|
|
flags &= ~flag;
|
2018-07-14 18:47:50 +08:00
|
|
|
} else if (opt_eq(s, len, "default_permissions") ||
|
|
|
|
opt_eq(s, len, "allow_other") ||
|
|
|
|
begins_with(s, "max_read=") ||
|
|
|
|
begins_with(s, "blksize=")) {
|
2007-12-12 22:25:40 +08:00
|
|
|
memcpy(d, s, len);
|
|
|
|
d += len;
|
|
|
|
*d++ = ',';
|
2018-07-14 18:47:50 +08:00
|
|
|
} else {
|
|
|
|
fprintf(stderr, "%s: unknown option '%.*s'\n", progname, len, s);
|
|
|
|
exit(1);
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s += len;
|
|
|
|
if (*s)
|
|
|
|
s++;
|
|
|
|
}
|
|
|
|
*d = '\0';
|
|
|
|
res = get_mnt_opts(flags, optbuf, &mnt_opts);
|
|
|
|
if (res == -1)
|
|
|
|
goto err;
|
|
|
|
|
2014-07-15 22:03:12 +08:00
|
|
|
sprintf(d, "fd=%i,rootmode=%o,user_id=%u,group_id=%u",
|
2007-12-12 22:25:40 +08:00
|
|
|
fd, rootmode, getuid(), getgid());
|
|
|
|
|
|
|
|
source = malloc((fsname ? strlen(fsname) : 0) +
|
|
|
|
(subtype ? strlen(subtype) : 0) + strlen(dev) + 32);
|
|
|
|
|
|
|
|
type = malloc((subtype ? strlen(subtype) : 0) + 32);
|
|
|
|
if (!type || !source) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subtype)
|
|
|
|
sprintf(type, "%s.%s", blkdev ? "fuseblk" : "fuse", subtype);
|
|
|
|
else
|
|
|
|
strcpy(type, blkdev ? "fuseblk" : "fuse");
|
|
|
|
|
|
|
|
if (fsname)
|
|
|
|
strcpy(source, fsname);
|
|
|
|
else
|
|
|
|
strcpy(source, subtype ? subtype : dev);
|
|
|
|
|
fusermount: prevent silent truncation of mount options
Currently, in the kernel, copy_mount_options() copies in one page of
userspace memory (or less if some of that memory area is not mapped).
do_mount() then writes a null byte to the last byte of the copied page.
This means that mount option strings longer than PAGE_SIZE-1 bytes get
truncated silently.
Therefore, this can happen:
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0
user@d9-ut:~$ fusermount -u mount
I'm not aware of any context in which this is actually exploitable - you'd
still need the UIDs to fit, and you can't do it if the three GIDs of the
process don't match (in the case of a typical setgid binary), but it does
look like something that should be fixed.
I also plan to try to get this fixed on the kernel side.
2018-07-14 05:51:17 +08:00
|
|
|
res = mount_notrunc(source, mnt, type, flags, optbuf);
|
2007-12-12 22:25:40 +08:00
|
|
|
if (res == -1 && errno == ENODEV && subtype) {
|
|
|
|
/* Probably missing subtype support */
|
|
|
|
strcpy(type, blkdev ? "fuseblk" : "fuse");
|
|
|
|
if (fsname) {
|
|
|
|
if (!blkdev)
|
|
|
|
sprintf(source, "%s#%s", subtype, fsname);
|
|
|
|
} else {
|
|
|
|
strcpy(source, type);
|
|
|
|
}
|
|
|
|
|
fusermount: prevent silent truncation of mount options
Currently, in the kernel, copy_mount_options() copies in one page of
userspace memory (or less if some of that memory area is not mapped).
do_mount() then writes a null byte to the last byte of the copied page.
This means that mount option strings longer than PAGE_SIZE-1 bytes get
truncated silently.
Therefore, this can happen:
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0
user@d9-ut:~$ fusermount -u mount
I'm not aware of any context in which this is actually exploitable - you'd
still need the UIDs to fit, and you can't do it if the three GIDs of the
process don't match (in the case of a typical setgid binary), but it does
look like something that should be fixed.
I also plan to try to get this fixed on the kernel side.
2018-07-14 05:51:17 +08:00
|
|
|
res = mount_notrunc(source, mnt, type, flags, optbuf);
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
if (res == -1 && errno == EINVAL) {
|
|
|
|
/* It could be an old version not supporting group_id */
|
2014-07-15 22:03:12 +08:00
|
|
|
sprintf(d, "fd=%i,rootmode=%o,user_id=%u",
|
2007-12-12 22:25:40 +08:00
|
|
|
fd, rootmode, getuid());
|
fusermount: prevent silent truncation of mount options
Currently, in the kernel, copy_mount_options() copies in one page of
userspace memory (or less if some of that memory area is not mapped).
do_mount() then writes a null byte to the last byte of the copied page.
This means that mount option strings longer than PAGE_SIZE-1 bytes get
truncated silently.
Therefore, this can happen:
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4000')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1000 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4050')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=100 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4051')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=10 0 0
user@d9-ut:~$ fusermount -u mount
user@d9-ut:~$ _FUSE_COMMFD=10000 fusermount -o "$(perl -e 'print ","x4052')" mount
sending file descriptor: Bad file descriptor
user@d9-ut:~$ grep /mount /proc/mounts
/dev/fuse /home/user/mount fuse rw,nosuid,nodev,relatime,user_id=1000,group_id=1 0 0
user@d9-ut:~$ fusermount -u mount
I'm not aware of any context in which this is actually exploitable - you'd
still need the UIDs to fit, and you can't do it if the three GIDs of the
process don't match (in the case of a typical setgid binary), but it does
look like something that should be fixed.
I also plan to try to get this fixed on the kernel side.
2018-07-14 05:51:17 +08:00
|
|
|
res = mount_notrunc(source, mnt, type, flags, optbuf);
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
if (res == -1) {
|
|
|
|
int errno_save = errno;
|
|
|
|
if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
|
|
|
|
fprintf(stderr, "%s: 'fuseblk' support missing\n",
|
|
|
|
progname);
|
|
|
|
else
|
|
|
|
fprintf(stderr, "%s: mount failed: %s\n", progname,
|
|
|
|
strerror(errno_save));
|
|
|
|
goto err;
|
|
|
|
}
|
2011-03-31 01:34:58 +08:00
|
|
|
*sourcep = source;
|
|
|
|
*typep = type;
|
|
|
|
*mnt_optsp = mnt_opts;
|
2008-03-26 05:20:40 +08:00
|
|
|
free(fsname);
|
2007-12-12 22:25:40 +08:00
|
|
|
free(optbuf);
|
|
|
|
|
2011-03-31 01:34:58 +08:00
|
|
|
return 0;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
err:
|
|
|
|
free(fsname);
|
|
|
|
free(subtype);
|
|
|
|
free(source);
|
|
|
|
free(type);
|
|
|
|
free(mnt_opts);
|
|
|
|
free(optbuf);
|
|
|
|
return -1;
|
2001-11-08 19:34:54 +08:00
|
|
|
}
|
|
|
|
|
2011-03-11 20:46:13 +08:00
|
|
|
static int check_perm(const char **mntp, struct stat *stbuf, int *mountpoint_fd)
|
2001-11-08 19:34:54 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int res;
|
|
|
|
const char *mnt = *mntp;
|
|
|
|
const char *origmnt = mnt;
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
struct statfs fs_buf;
|
|
|
|
size_t i;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
res = lstat(mnt, stbuf);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No permission checking is done for root */
|
|
|
|
if (getuid() == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (S_ISDIR(stbuf->st_mode)) {
|
2011-03-11 20:46:13 +08:00
|
|
|
res = chdir(mnt);
|
2007-12-12 22:25:40 +08:00
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr,
|
2011-03-11 20:46:13 +08:00
|
|
|
"%s: failed to chdir to mountpoint: %s\n",
|
2007-12-12 22:25:40 +08:00
|
|
|
progname, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
mnt = *mntp = ".";
|
|
|
|
res = lstat(mnt, stbuf);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: failed to access mountpoint %s: %s\n",
|
|
|
|
progname, origmnt, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((stbuf->st_mode & S_ISVTX) && stbuf->st_uid != getuid()) {
|
|
|
|
fprintf(stderr, "%s: mountpoint %s not owned by user\n",
|
|
|
|
progname, origmnt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = access(mnt, W_OK);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: user has no write access to mountpoint %s\n",
|
|
|
|
progname, origmnt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
} else if (S_ISREG(stbuf->st_mode)) {
|
|
|
|
static char procfile[256];
|
|
|
|
*mountpoint_fd = open(mnt, O_WRONLY);
|
|
|
|
if (*mountpoint_fd == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
res = fstat(*mountpoint_fd, stbuf);
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: failed to access mountpoint %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (!S_ISREG(stbuf->st_mode)) {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: mountpoint %s is no longer a regular file\n",
|
|
|
|
progname, mnt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
sprintf(procfile, "/proc/self/fd/%i", *mountpoint_fd);
|
|
|
|
*mntp = procfile;
|
|
|
|
} else {
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: mountpoint %s is not a directory or a regular file\n",
|
|
|
|
progname, mnt);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
/* Do not permit mounting over anything in procfs - it has a couple
|
|
|
|
* places to which we have "write access" without being supposed to be
|
|
|
|
* able to just put anything we want there.
|
|
|
|
* Luckily, without allow_other, we can't get other users to actually
|
|
|
|
* use any fake information we try to put there anyway.
|
|
|
|
* Use a whitelist to be safe. */
|
|
|
|
if (statfs(*mntp, &fs_buf)) {
|
|
|
|
fprintf(stderr, "%s: failed to access mountpoint %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
return -1;
|
|
|
|
}
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2018-08-03 23:22:32 +08:00
|
|
|
/* Define permitted filesystems for the mount target. This was
|
|
|
|
* originally the same list as used by the ecryptfs mount helper
|
|
|
|
* (https://bazaar.launchpad.net/~ecryptfs/ecryptfs/trunk/view/head:/src/utils/mount.ecryptfs_private.c#L225)
|
|
|
|
* but got expanded as we found more filesystems that needed to be
|
2023-03-30 02:47:13 +08:00
|
|
|
* overlaid. */
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
typeof(fs_buf.f_type) f_type_whitelist[] = {
|
|
|
|
0x61756673 /* AUFS_SUPER_MAGIC */,
|
2018-08-03 23:22:32 +08:00
|
|
|
0x00000187 /* AUTOFS_SUPER_MAGIC */,
|
2018-08-09 09:24:44 +08:00
|
|
|
0xCA451A4E /* BCACHEFS_STATFS_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x9123683E /* BTRFS_SUPER_MAGIC */,
|
|
|
|
0x00C36400 /* CEPH_SUPER_MAGIC */,
|
|
|
|
0xFF534D42 /* CIFS_MAGIC_NUMBER */,
|
|
|
|
0x0000F15F /* ECRYPTFS_SUPER_MAGIC */,
|
2020-12-19 18:43:19 +08:00
|
|
|
0X2011BAB0 /* EXFAT_SUPER_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x0000EF53 /* EXT[234]_SUPER_MAGIC */,
|
|
|
|
0xF2F52010 /* F2FS_SUPER_MAGIC */,
|
|
|
|
0x65735546 /* FUSE_SUPER_MAGIC */,
|
|
|
|
0x01161970 /* GFS2_MAGIC */,
|
2018-10-20 05:19:28 +08:00
|
|
|
0x47504653 /* GPFS_SUPER_MAGIC */,
|
2019-04-04 04:43:48 +08:00
|
|
|
0x0000482b /* HFSPLUS_SUPER_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x000072B6 /* JFFS2_SUPER_MAGIC */,
|
2019-04-04 04:43:48 +08:00
|
|
|
0x3153464A /* JFS_SUPER_MAGIC */,
|
2018-10-20 05:19:28 +08:00
|
|
|
0x0BD00BD0 /* LL_SUPER_MAGIC */,
|
2019-04-04 04:43:48 +08:00
|
|
|
0X00004D44 /* MSDOS_SUPER_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x0000564C /* NCP_SUPER_MAGIC */,
|
|
|
|
0x00006969 /* NFS_SUPER_MAGIC */,
|
|
|
|
0x00003434 /* NILFS_SUPER_MAGIC */,
|
|
|
|
0x5346544E /* NTFS_SB_MAGIC */,
|
2019-04-04 04:43:48 +08:00
|
|
|
0x5346414f /* OPENAFS_SUPER_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x794C7630 /* OVERLAYFS_SUPER_MAGIC */,
|
|
|
|
0x52654973 /* REISERFS_SUPER_MAGIC */,
|
2019-04-04 04:43:48 +08:00
|
|
|
0xFE534D42 /* SMB2_SUPER_MAGIC */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x73717368 /* SQUASHFS_MAGIC */,
|
|
|
|
0x01021994 /* TMPFS_MAGIC */,
|
|
|
|
0x24051905 /* UBIFS_SUPER_MAGIC */,
|
2019-09-15 23:56:56 +08:00
|
|
|
0x736675005346544e /* UFSD */,
|
fusermount: whitelist known-good filesystems for mountpoints
Before:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
mount("/dev/fuse", ".", "fuse", MS_NOSUID|MS_NODEV, "fd=3,rootmode=40000,user_id=379777,group_id=5001") = 0
sending file descriptor: Socket operation on non-socket
+++ exited with 1 +++
After:
$ _FUSE_COMMFD=1 priv_strace -s8000 -e trace=mount util/fusermount3 /proc/self/fd
util/fusermount3: mounting over filesystem type 0x009fa0 is forbidden
+++ exited with 1 +++
This patch could potentially have security
impact on some systems that are configured with allow_other;
see https://launchpad.net/bugs/1530566 for an example of how a similar
issue in the ecryptfs mount helper was exploitable. However, the FUSE
mount helper performs slightly different security checks, so that exact
attack doesn't work with fusermount; I don't know of any specific attack
you could perform using this, apart from faking the SELinux context of your
process when someone's looking at a process listing. Potential targets for
overwrite are (looking on a system with a 4.9 kernel):
writable only for the current process:
/proc/self/{fd,map_files}
(Yes, "ls -l" claims that you don't have write access, but that's not true;
"find -writable" will show you what access you really have.)
writable also for other owned processes:
/proc/$pid/{sched,autogroup,comm,mem,clear_refs,attr/*,oom_adj,
oom_score_adj,loginuid,coredump_filter,uid_map,gid_map,projid_map,
setgroups,timerslack_ns}
2018-07-14 19:37:41 +08:00
|
|
|
0x58465342 /* XFS_SB_MAGIC */,
|
|
|
|
0x2FC12FC1 /* ZFS_SUPER_MAGIC */,
|
|
|
|
};
|
|
|
|
for (i = 0; i < sizeof(f_type_whitelist)/sizeof(f_type_whitelist[0]); i++) {
|
|
|
|
if (f_type_whitelist[i] == fs_buf.f_type)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
fprintf(stderr, "%s: mounting over filesystem type %#010lx is forbidden\n",
|
|
|
|
progname, (unsigned long)fs_buf.f_type);
|
|
|
|
return -1;
|
2001-11-08 19:34:54 +08:00
|
|
|
}
|
|
|
|
|
2004-11-20 19:18:34 +08:00
|
|
|
static int try_open(const char *dev, char **devp, int silent)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int fd = open(dev, O_RDWR);
|
|
|
|
if (fd != -1) {
|
|
|
|
*devp = strdup(dev);
|
|
|
|
if (*devp == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n",
|
|
|
|
progname);
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
}
|
|
|
|
} else if (errno == ENODEV ||
|
|
|
|
errno == ENOENT)/* check for ENOENT too, for the udev case */
|
|
|
|
return -2;
|
|
|
|
else if (!silent) {
|
|
|
|
fprintf(stderr, "%s: failed to open %s: %s\n", progname, dev,
|
|
|
|
strerror(errno));
|
|
|
|
}
|
|
|
|
return fd;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
2005-06-08 19:01:17 +08:00
|
|
|
static int try_open_fuse_device(char **devp)
|
2001-11-08 19:34:54 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int fd;
|
2004-11-20 19:18:34 +08:00
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
drop_privs();
|
2016-10-28 11:54:45 +08:00
|
|
|
fd = try_open(FUSE_DEV, devp, 0);
|
2007-12-12 22:25:40 +08:00
|
|
|
restore_privs();
|
2016-10-28 11:54:45 +08:00
|
|
|
return fd;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int open_fuse_device(char **devp)
|
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int fd = try_open_fuse_device(devp);
|
|
|
|
if (fd >= -1)
|
|
|
|
return fd;
|
2005-02-02 19:14:04 +08:00
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
fprintf(stderr,
|
|
|
|
"%s: fuse device not found, try 'modprobe fuse' first\n",
|
|
|
|
progname);
|
2006-08-19 03:26:23 +08:00
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
return -1;
|
2004-11-20 19:18:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
static int mount_fuse(const char *mnt, const char *opts, const char **type)
|
2004-11-20 19:18:34 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int res;
|
|
|
|
int fd;
|
|
|
|
char *dev;
|
|
|
|
struct stat stbuf;
|
|
|
|
char *source = NULL;
|
|
|
|
char *mnt_opts = NULL;
|
|
|
|
const char *real_mnt = mnt;
|
|
|
|
int mountpoint_fd = -1;
|
|
|
|
|
|
|
|
fd = open_fuse_device(&dev);
|
|
|
|
if (fd == -1)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
drop_privs();
|
|
|
|
read_conf();
|
|
|
|
|
|
|
|
if (getuid() != 0 && mount_max != -1) {
|
|
|
|
int mount_count = count_fuse_fs();
|
|
|
|
if (mount_count >= mount_max) {
|
2016-11-29 13:33:14 +08:00
|
|
|
fprintf(stderr, "%s: too many FUSE filesystems mounted; mount_max=N can be set in %s\n", progname, FUSE_CONF);
|
2011-03-14 21:52:39 +08:00
|
|
|
goto fail_close_fd;
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-28 11:54:45 +08:00
|
|
|
res = check_perm(&real_mnt, &stbuf, &mountpoint_fd);
|
|
|
|
restore_privs();
|
|
|
|
if (res != -1)
|
2018-10-17 07:23:07 +08:00
|
|
|
res = do_mount(real_mnt, type, stbuf.st_mode & S_IFMT,
|
2016-10-28 11:54:45 +08:00
|
|
|
fd, opts, dev, &source, &mnt_opts);
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
if (mountpoint_fd != -1)
|
2011-03-11 20:46:13 +08:00
|
|
|
close(mountpoint_fd);
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2011-03-14 21:52:39 +08:00
|
|
|
if (res == -1)
|
|
|
|
goto fail_close_fd;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2011-05-20 22:01:38 +08:00
|
|
|
res = chdir("/");
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
|
|
|
|
goto fail_close_fd;
|
|
|
|
}
|
|
|
|
|
2007-12-12 22:25:40 +08:00
|
|
|
if (geteuid() == 0) {
|
2018-10-17 07:23:07 +08:00
|
|
|
res = add_mount(source, mnt, *type, mnt_opts);
|
2007-12-12 22:25:40 +08:00
|
|
|
if (res == -1) {
|
2011-03-11 20:51:49 +08:00
|
|
|
/* Can't clean up mount in a non-racy way */
|
2011-03-14 21:52:39 +08:00
|
|
|
goto fail_close_fd;
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-03-14 21:52:39 +08:00
|
|
|
out_free:
|
2007-12-12 22:25:40 +08:00
|
|
|
free(source);
|
|
|
|
free(mnt_opts);
|
|
|
|
free(dev);
|
|
|
|
|
|
|
|
return fd;
|
2011-03-14 21:52:39 +08:00
|
|
|
|
|
|
|
fail_close_fd:
|
|
|
|
close(fd);
|
|
|
|
fd = -1;
|
|
|
|
goto out_free;
|
2001-11-08 19:34:54 +08:00
|
|
|
}
|
|
|
|
|
2005-02-02 19:14:04 +08:00
|
|
|
static int send_fd(int sock_fd, int fd)
|
2002-10-25 19:40:14 +08:00
|
|
|
{
|
2007-12-12 22:25:40 +08:00
|
|
|
int retval;
|
|
|
|
struct msghdr msg;
|
|
|
|
struct cmsghdr *p_cmsg;
|
|
|
|
struct iovec vec;
|
|
|
|
size_t cmsgbuf[CMSG_SPACE(sizeof(fd)) / sizeof(size_t)];
|
|
|
|
int *p_fds;
|
|
|
|
char sendchar = 0;
|
|
|
|
|
|
|
|
msg.msg_control = cmsgbuf;
|
|
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
p_cmsg = CMSG_FIRSTHDR(&msg);
|
|
|
|
p_cmsg->cmsg_level = SOL_SOCKET;
|
|
|
|
p_cmsg->cmsg_type = SCM_RIGHTS;
|
|
|
|
p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
|
|
|
|
p_fds = (int *) CMSG_DATA(p_cmsg);
|
|
|
|
*p_fds = fd;
|
|
|
|
msg.msg_controllen = p_cmsg->cmsg_len;
|
|
|
|
msg.msg_name = NULL;
|
|
|
|
msg.msg_namelen = 0;
|
|
|
|
msg.msg_iov = &vec;
|
|
|
|
msg.msg_iovlen = 1;
|
|
|
|
msg.msg_flags = 0;
|
|
|
|
/* "To pass file descriptors or credentials you need to send/read at
|
|
|
|
* least one byte" (man 7 unix) */
|
|
|
|
vec.iov_base = &sendchar;
|
|
|
|
vec.iov_len = sizeof(sendchar);
|
|
|
|
while ((retval = sendmsg(sock_fd, &msg, 0)) == -1 && errno == EINTR);
|
|
|
|
if (retval != 1) {
|
|
|
|
perror("sending file descriptor");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 0;
|
2002-10-25 19:40:14 +08:00
|
|
|
}
|
|
|
|
|
2023-03-28 17:45:55 +08:00
|
|
|
/* Helper for should_auto_unmount
|
|
|
|
*
|
|
|
|
* fusermount typically has the s-bit set - initial open of `mnt` was as root
|
|
|
|
* and got EACCESS as 'allow_other' was not specified.
|
|
|
|
* Try opening `mnt` again with uid and guid of the calling process.
|
|
|
|
*/
|
|
|
|
static int recheck_ENOTCONN_as_owner(const char *mnt)
|
Fix `auto_unmount` to work without `allow_other`
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.
In this commit, we work around this limitation:
We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.
In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`. (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.) As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.
Please give feedback on correctness, style and suggest tests.
Fixes https://github.com/libfuse/libfuse/issues/586
2023-03-27 18:43:26 +08:00
|
|
|
{
|
|
|
|
int pid = fork();
|
|
|
|
if(pid == -1) {
|
2023-03-28 17:45:55 +08:00
|
|
|
perror("fuse: recheck_ENOTCONN_as_owner can't fork");
|
Fix `auto_unmount` to work without `allow_other`
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.
In this commit, we work around this limitation:
We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.
In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`. (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.) As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.
Please give feedback on correctness, style and suggest tests.
Fixes https://github.com/libfuse/libfuse/issues/586
2023-03-27 18:43:26 +08:00
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
} else if(pid == 0) {
|
|
|
|
uid_t uid = getuid();
|
|
|
|
gid_t gid = getgid();
|
|
|
|
if(setresgid(gid, gid, gid) == -1) {
|
|
|
|
perror("fuse: can't set resgid");
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
if(setresuid(uid, uid, uid) == -1) {
|
|
|
|
perror("fuse: can't set resuid");
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
int fd = open(mnt, O_RDONLY);
|
|
|
|
if(fd == -1 && errno == ENOTCONN)
|
|
|
|
_exit(EXIT_SUCCESS);
|
|
|
|
else
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
} else {
|
|
|
|
int status;
|
|
|
|
int res = waitpid(pid, &status, 0);
|
|
|
|
if (res == -1) {
|
|
|
|
perror("fuse: waiting for child failed");
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
/* The parent fuse process has died: decide whether to auto_unmount.
|
|
|
|
*
|
|
|
|
* In the normal case (umount or fusermount -u), the filesystem
|
|
|
|
* has already been unmounted. If we simply unmount again we can
|
|
|
|
* cause problems with stacked mounts (e.g. autofs).
|
|
|
|
*
|
|
|
|
* So we unmount here only in abnormal case where fuse process has
|
|
|
|
* died without unmount happening. To detect this, we first look in
|
|
|
|
* the mount table to make sure the mountpoint is still mounted and
|
|
|
|
* has proper type. If so, we then see if opening the mount dir is
|
|
|
|
* returning 'Transport endpoint is not connected'.
|
|
|
|
*
|
|
|
|
* The order of these is important, because if autofs is in use,
|
|
|
|
* opening the dir to check for ENOTCONN will cause a new mount
|
|
|
|
* in the normal case where filesystem has been unmounted cleanly.
|
|
|
|
*/
|
|
|
|
static int should_auto_unmount(const char *mnt, const char *type)
|
|
|
|
{
|
|
|
|
char *copy;
|
|
|
|
const char *last;
|
|
|
|
int result = 0;
|
|
|
|
int fd;
|
|
|
|
|
|
|
|
copy = strdup(mnt);
|
|
|
|
if (copy == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", progname);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (chdir_to_parent(copy, &last) == -1)
|
|
|
|
goto out;
|
|
|
|
if (check_is_mount(last, mnt, type) == -1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
fd = open(mnt, O_RDONLY);
|
Fix `auto_unmount` to work without `allow_other`
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.
In this commit, we work around this limitation:
We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.
In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`. (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.) As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.
Please give feedback on correctness, style and suggest tests.
Fixes https://github.com/libfuse/libfuse/issues/586
2023-03-27 18:43:26 +08:00
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
if (fd != -1) {
|
|
|
|
close(fd);
|
|
|
|
} else {
|
Fix `auto_unmount` to work without `allow_other`
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.
In this commit, we work around this limitation:
We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.
In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`. (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.) As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.
Please give feedback on correctness, style and suggest tests.
Fixes https://github.com/libfuse/libfuse/issues/586
2023-03-27 18:43:26 +08:00
|
|
|
switch(errno) {
|
|
|
|
case ENOTCONN:
|
|
|
|
result = 1;
|
|
|
|
break;
|
|
|
|
case EACCES:
|
2023-03-28 17:45:55 +08:00
|
|
|
result = recheck_ENOTCONN_as_owner(mnt);
|
Fix `auto_unmount` to work without `allow_other`
In
https://github.com/libfuse/libfuse/blob/77d662459a0fcdf358d515477d33795837e859d5/util/fusermount.c#L1219
`open` is executed as root which does not have access to the mount
point if `allow_other` was not used and the real user id is not 0. Since
`allow_other` usually cannot be specified by unprivileged users,
`auto_unmount` has no effect for unprivileged users.
In this commit, we work around this limitation:
We first try to open the mountpoint as root, and if we get `EACCES`, we
retry as the user who started fusermount, and see if we get `ENOTCONN`.
In my testing, I found that `setfsuid` and `setfsgid` don't work to get
around the lack of `allow_other`. (Sorry, I don't know enough about the
Linux kernel to tell whether that's significant.) As a workaround, I
decided to use `setresuid` and `setresgid` in a forked child process,
and communicate via its exit status.
Please give feedback on correctness, style and suggest tests.
Fixes https://github.com/libfuse/libfuse/issues/586
2023-03-27 18:43:26 +08:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
result = 0;
|
|
|
|
break;
|
|
|
|
}
|
2018-10-17 07:23:07 +08:00
|
|
|
}
|
|
|
|
out:
|
|
|
|
free(copy);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2005-07-15 17:59:59 +08:00
|
|
|
static void usage(void)
|
2001-11-08 19:34:54 +08:00
|
|
|
{
|
2013-07-26 22:20:28 +08:00
|
|
|
printf("%s: [options] mountpoint\n"
|
|
|
|
"Options:\n"
|
|
|
|
" -h print help\n"
|
|
|
|
" -V print version\n"
|
2020-03-14 00:48:09 +08:00
|
|
|
" -o opt[,opt...] mount options\n"
|
2013-07-26 22:20:28 +08:00
|
|
|
" -u unmount\n"
|
|
|
|
" -q quiet\n"
|
|
|
|
" -z lazy unmount\n",
|
|
|
|
progname);
|
2007-12-12 22:25:40 +08:00
|
|
|
exit(1);
|
2001-11-08 19:34:54 +08:00
|
|
|
}
|
|
|
|
|
2005-07-15 17:59:59 +08:00
|
|
|
static void show_version(void)
|
|
|
|
{
|
2016-11-29 13:33:14 +08:00
|
|
|
printf("fusermount3 version: %s\n", PACKAGE_VERSION);
|
2007-12-12 22:25:40 +08:00
|
|
|
exit(0);
|
2005-07-15 17:59:59 +08:00
|
|
|
}
|
|
|
|
|
2001-11-08 19:34:54 +08:00
|
|
|
int main(int argc, char *argv[])
|
|
|
|
{
|
2011-03-12 14:59:14 +08:00
|
|
|
sigset_t sigset;
|
2007-12-12 22:25:40 +08:00
|
|
|
int ch;
|
|
|
|
int fd;
|
|
|
|
int res;
|
|
|
|
char *origmnt;
|
|
|
|
char *mnt;
|
|
|
|
static int unmount = 0;
|
|
|
|
static int lazy = 0;
|
|
|
|
static int quiet = 0;
|
|
|
|
char *commfd;
|
|
|
|
int cfd;
|
|
|
|
const char *opts = "";
|
2018-10-17 07:23:07 +08:00
|
|
|
const char *type = NULL;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
static const struct option long_opts[] = {
|
|
|
|
{"unmount", no_argument, NULL, 'u'},
|
|
|
|
{"lazy", no_argument, NULL, 'z'},
|
|
|
|
{"quiet", no_argument, NULL, 'q'},
|
|
|
|
{"help", no_argument, NULL, 'h'},
|
|
|
|
{"version", no_argument, NULL, 'V'},
|
|
|
|
{0, 0, 0, 0}};
|
|
|
|
|
2021-01-08 18:07:02 +08:00
|
|
|
progname = strdup(argc > 0 ? argv[0] : "fusermount");
|
2007-12-12 22:25:40 +08:00
|
|
|
if (progname == NULL) {
|
|
|
|
fprintf(stderr, "%s: failed to allocate memory\n", argv[0]);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
while ((ch = getopt_long(argc, argv, "hVo:uzq", long_opts,
|
|
|
|
NULL)) != -1) {
|
|
|
|
switch (ch) {
|
|
|
|
case 'h':
|
|
|
|
usage();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'V':
|
|
|
|
show_version();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'o':
|
|
|
|
opts = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'u':
|
|
|
|
unmount = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'z':
|
|
|
|
lazy = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'q':
|
|
|
|
quiet = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lazy && !unmount) {
|
|
|
|
fprintf(stderr, "%s: -z can only be used with -u\n", progname);
|
|
|
|
exit(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind >= argc) {
|
|
|
|
fprintf(stderr, "%s: missing mountpoint argument\n", progname);
|
|
|
|
exit(1);
|
2009-07-02 20:52:27 +08:00
|
|
|
} else if (argc > optind + 1) {
|
|
|
|
fprintf(stderr, "%s: extra arguments after the mountpoint\n",
|
|
|
|
progname);
|
|
|
|
exit(1);
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
origmnt = argv[optind];
|
|
|
|
|
|
|
|
drop_privs();
|
|
|
|
mnt = fuse_mnt_resolve_path(progname, origmnt);
|
2011-01-31 19:36:32 +08:00
|
|
|
if (mnt != NULL) {
|
|
|
|
res = chdir("/");
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2011-01-31 19:36:32 +08:00
|
|
|
}
|
|
|
|
}
|
2007-12-12 22:25:40 +08:00
|
|
|
restore_privs();
|
|
|
|
if (mnt == NULL)
|
|
|
|
exit(1);
|
|
|
|
|
|
|
|
umask(033);
|
2011-03-12 14:59:14 +08:00
|
|
|
if (unmount)
|
|
|
|
goto do_unmount;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
commfd = getenv(FUSE_COMMFD_ENV);
|
|
|
|
if (commfd == NULL) {
|
|
|
|
fprintf(stderr, "%s: old style mounting not supported\n",
|
|
|
|
progname);
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2007-12-12 22:25:40 +08:00
|
|
|
}
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
fd = mount_fuse(mnt, opts, &type);
|
2007-12-12 22:25:40 +08:00
|
|
|
if (fd == -1)
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2007-12-12 22:25:40 +08:00
|
|
|
|
|
|
|
cfd = atoi(commfd);
|
|
|
|
res = send_fd(cfd, fd);
|
|
|
|
if (res == -1)
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2011-03-12 14:59:14 +08:00
|
|
|
close(fd);
|
|
|
|
|
2016-10-29 05:54:20 +08:00
|
|
|
if (!auto_unmount) {
|
|
|
|
free(mnt);
|
2011-03-12 14:59:14 +08:00
|
|
|
return 0;
|
2016-10-29 05:54:20 +08:00
|
|
|
}
|
2011-03-12 14:59:14 +08:00
|
|
|
|
2011-05-23 22:10:35 +08:00
|
|
|
/* Become a daemon and wait for the parent to exit or die.
|
2016-10-08 11:57:53 +08:00
|
|
|
ie For the control socket to get closed.
|
2011-03-12 14:59:14 +08:00
|
|
|
btw We don't want to use daemon() function here because
|
|
|
|
it forks and messes with the file descriptors. */
|
|
|
|
setsid();
|
2011-05-20 22:01:38 +08:00
|
|
|
res = chdir("/");
|
|
|
|
if (res == -1) {
|
|
|
|
fprintf(stderr, "%s: failed to chdir to '/'\n", progname);
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2011-05-20 22:01:38 +08:00
|
|
|
}
|
2011-03-12 14:59:14 +08:00
|
|
|
|
|
|
|
sigfillset(&sigset);
|
|
|
|
sigprocmask(SIG_BLOCK, &sigset, NULL);
|
2007-12-12 22:25:40 +08:00
|
|
|
|
2011-03-12 14:59:14 +08:00
|
|
|
lazy = 1;
|
|
|
|
quiet = 1;
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
unsigned char buf[16];
|
|
|
|
int n = recv(cfd, buf, sizeof(buf), 0);
|
|
|
|
if (!n)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (n < 0) {
|
|
|
|
if (errno == EINTR)
|
|
|
|
continue;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-17 07:23:07 +08:00
|
|
|
if (!should_auto_unmount(mnt, type)) {
|
|
|
|
goto success_out;
|
|
|
|
}
|
|
|
|
|
2011-03-12 14:59:14 +08:00
|
|
|
do_unmount:
|
|
|
|
if (geteuid() == 0)
|
|
|
|
res = unmount_fuse(mnt, quiet, lazy);
|
|
|
|
else {
|
|
|
|
res = umount2(mnt, lazy ? UMOUNT_DETACH : 0);
|
|
|
|
if (res == -1 && !quiet)
|
|
|
|
fprintf(stderr,
|
|
|
|
"%s: failed to unmount %s: %s\n",
|
|
|
|
progname, mnt, strerror(errno));
|
|
|
|
}
|
|
|
|
if (res == -1)
|
2016-10-29 05:54:20 +08:00
|
|
|
goto err_out;
|
2018-10-17 07:23:07 +08:00
|
|
|
|
|
|
|
success_out:
|
2018-07-22 05:14:06 +08:00
|
|
|
free(mnt);
|
2007-12-12 22:25:40 +08:00
|
|
|
return 0;
|
2016-10-29 05:54:20 +08:00
|
|
|
|
|
|
|
err_out:
|
|
|
|
free(mnt);
|
|
|
|
exit(1);
|
2001-11-08 19:34:54 +08:00
|
|
|
}
|