fix unprivileged mount/unmount
parent
6b52c58799
commit
fca849d365
|
@ -92,7 +92,8 @@ typedef enum {
|
|||
NTFS_VOLUME_UNKNOWN_REASON = 18,
|
||||
NTFS_VOLUME_NO_PRIVILEGE = 19,
|
||||
NTFS_VOLUME_OUT_OF_MEMORY = 20,
|
||||
NTFS_VOLUME_FUSE_ERROR = 21
|
||||
NTFS_VOLUME_FUSE_ERROR = 21,
|
||||
NTFS_VOLUME_INSECURE = 22
|
||||
} ntfs_volume_status;
|
||||
|
||||
/**
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <sys/fsuid.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <grp.h>
|
||||
|
||||
#define FUSE_DEV_NEW "/dev/fuse"
|
||||
#define FUSE_CONF "/etc/fuse.conf"
|
||||
|
@ -50,23 +51,85 @@ static const char *get_user_name(void)
|
|||
}
|
||||
}
|
||||
|
||||
static uid_t oldfsuid;
|
||||
static gid_t oldfsgid;
|
||||
|
||||
static void drop_privs(void)
|
||||
int drop_privs(void)
|
||||
{
|
||||
if (getuid() != 0) {
|
||||
oldfsuid = setfsuid(getuid());
|
||||
oldfsgid = setfsgid(getgid());
|
||||
}
|
||||
if (!geteuid()) {
|
||||
if (setgroups(0, NULL) < 0) {
|
||||
perror("priv drop: setgroups failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!getegid()) {
|
||||
|
||||
gid_t new_gid = getgid();
|
||||
|
||||
if (setresgid(-1, new_gid, getegid()) < 0) {
|
||||
perror("priv drop: setresgid failed");
|
||||
return -1;
|
||||
}
|
||||
if (getegid() != new_gid){
|
||||
perror("dropping group privilege failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!geteuid()) {
|
||||
|
||||
uid_t new_uid = getuid();
|
||||
|
||||
if (setresuid(-1, new_uid, geteuid()) < 0) {
|
||||
perror("priv drop: setresuid failed");
|
||||
return -1;
|
||||
}
|
||||
if (geteuid() != new_uid){
|
||||
perror("dropping user privilege failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void restore_privs(void)
|
||||
int restore_privs(void)
|
||||
{
|
||||
if (getuid() != 0) {
|
||||
setfsuid(oldfsuid);
|
||||
setfsgid(oldfsgid);
|
||||
}
|
||||
if (geteuid()) {
|
||||
|
||||
uid_t ruid, euid, suid;
|
||||
|
||||
if (getresuid(&ruid, &euid, &suid) < 0) {
|
||||
perror("priv restore: getresuid failed");
|
||||
return -1;
|
||||
}
|
||||
if (setresuid(-1, suid, -1) < 0) {
|
||||
perror("priv restore: setresuid failed");
|
||||
return -1;
|
||||
}
|
||||
if (geteuid() != suid) {
|
||||
perror("restoring privilege failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (getegid()) {
|
||||
|
||||
gid_t rgid, egid, sgid;
|
||||
|
||||
if (getresgid(&rgid, &egid, &sgid) < 0) {
|
||||
perror("priv restore: getresgid failed");
|
||||
return -1;
|
||||
}
|
||||
if (setresgid(-1, sgid, -1) < 0) {
|
||||
perror("priv restore: setresgid failed");
|
||||
return -1;
|
||||
}
|
||||
if (getegid() != sgid){
|
||||
perror("restoring group privilege failed");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifndef IGNORE_MTAB
|
||||
|
@ -76,66 +139,6 @@ static int add_mount(const char *source, const char *mnt, const char *type,
|
|||
return fuse_mnt_add_mount(progname, source, mnt, type, opts);
|
||||
}
|
||||
|
||||
static int unmount_fuse(const char *mnt, int quiet, int lazy)
|
||||
{
|
||||
if (getuid() != 0) {
|
||||
struct mntent *entp;
|
||||
FILE *fp;
|
||||
const char *user = NULL;
|
||||
char uidstr[32];
|
||||
unsigned uidlen = 0;
|
||||
int found;
|
||||
const char *mtab = _PATH_MOUNTED;
|
||||
|
||||
user = get_user_name();
|
||||
if (user == NULL)
|
||||
return -1;
|
||||
|
||||
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;
|
||||
while ((entp = getmntent(fp)) != NULL) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
endmntent(fp);
|
||||
|
||||
if (!found) {
|
||||
if (!quiet)
|
||||
fprintf(stderr, "%s: entry for %s not found in %s\n", progname,
|
||||
mnt, mtab);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return fuse_mnt_umount(progname, mnt, lazy);
|
||||
}
|
||||
|
||||
static int count_fuse_fs(void)
|
||||
{
|
||||
struct mntent *entp;
|
||||
|
@ -172,11 +175,6 @@ static int add_mount(const char *source, const char *mnt, const char *type,
|
|||
(void) opts;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int unmount_fuse(const char *mnt, int quiet, int lazy)
|
||||
{
|
||||
return fuse_mnt_umount(progname, mnt, lazy);
|
||||
}
|
||||
#endif /* IGNORE_MTAB */
|
||||
|
||||
static void strip_line(char *line)
|
||||
|
@ -458,12 +456,19 @@ static int do_mount(const char *mnt, char **typep, mode_t rootmode,
|
|||
else
|
||||
strcpy(source, dev);
|
||||
|
||||
if (restore_privs())
|
||||
goto err;
|
||||
|
||||
res = mount(source, mnt, type, flags, optbuf);
|
||||
if (res == -1 && errno == EINVAL) {
|
||||
/* It could be an old version not supporting group_id */
|
||||
sprintf(d, "fd=%i,rootmode=%o,user_id=%i", fd, rootmode, getuid());
|
||||
res = mount(source, mnt, type, flags, optbuf);
|
||||
}
|
||||
|
||||
if (drop_privs())
|
||||
goto err;
|
||||
|
||||
if (res == -1) {
|
||||
int errno_save = errno;
|
||||
if (blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
|
||||
|
@ -471,8 +476,9 @@ static int do_mount(const char *mnt, char **typep, mode_t rootmode,
|
|||
else {
|
||||
fprintf(stderr, "%s: mount failed: %s\n", progname, strerror(errno_save));
|
||||
if (errno_save == EPERM)
|
||||
fprintf(stderr, "No privilege to mount. Please see "
|
||||
"http://ntfs-3g.org/support.html#useroption\n");
|
||||
fprintf(stderr, "User doesn't have privilege to mount. "
|
||||
"For more information\nplease see: "
|
||||
"http://ntfs-3g.org/support.html#unprivileged\n");
|
||||
}
|
||||
goto err;
|
||||
} else {
|
||||
|
@ -629,24 +635,20 @@ static int mount_fuse(const char *mnt, const char *opts)
|
|||
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) {
|
||||
if (count_fuse_fs() >= mount_max) {
|
||||
fprintf(stderr, "%s: too many FUSE filesystems mounted; "
|
||||
"mount_max=N can be set in /etc/fuse.conf\n", progname);
|
||||
close(fd);
|
||||
return -1;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
res = check_perm(&real_mnt, &stbuf, &currdir_fd, &mountpoint_fd);
|
||||
restore_privs();
|
||||
if (res != -1)
|
||||
res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, dev,
|
||||
&source, &mnt_opts);
|
||||
res = do_mount(real_mnt, &type, stbuf.st_mode & S_IFMT, fd, opts, dev,
|
||||
&source, &mnt_opts);
|
||||
|
||||
if (currdir_fd != -1) {
|
||||
fchdir(currdir_fd);
|
||||
|
@ -655,59 +657,69 @@ static int mount_fuse(const char *mnt, const char *opts)
|
|||
if (mountpoint_fd != -1)
|
||||
close(mountpoint_fd);
|
||||
|
||||
if (res == -1) {
|
||||
close(fd);
|
||||
return -1;
|
||||
}
|
||||
if (res == -1)
|
||||
goto err;
|
||||
|
||||
if (restore_privs())
|
||||
goto err;
|
||||
|
||||
if (geteuid() == 0) {
|
||||
res = add_mount(source, mnt, type, mnt_opts);
|
||||
if (res == -1) {
|
||||
umount2(mnt, 2); /* lazy umount */
|
||||
close(fd);
|
||||
return -1;
|
||||
drop_privs();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if (drop_privs())
|
||||
goto err;
|
||||
out:
|
||||
free(source);
|
||||
free(type);
|
||||
free(mnt_opts);
|
||||
free(dev);
|
||||
|
||||
return fd;
|
||||
err:
|
||||
close(fd);
|
||||
fd = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
int fusermount(int unmount, int quiet, int lazy, const char *opts,
|
||||
const char *origmnt)
|
||||
{
|
||||
int res;
|
||||
int res = -1;
|
||||
char *mnt;
|
||||
mode_t old_umask;
|
||||
|
||||
if (lazy && !unmount) {
|
||||
fprintf(stderr, "%s: -z can only be used with -u\n", progname);
|
||||
return -1;
|
||||
}
|
||||
|
||||
drop_privs();
|
||||
mnt = fuse_mnt_resolve_path(progname, origmnt);
|
||||
restore_privs();
|
||||
if (mnt == NULL)
|
||||
return -1;
|
||||
|
||||
old_umask = umask(033);
|
||||
|
||||
if (unmount) {
|
||||
|
||||
if (restore_privs())
|
||||
goto out;
|
||||
|
||||
if (geteuid() == 0)
|
||||
res = unmount_fuse(mnt, quiet, lazy);
|
||||
res = fuse_mnt_umount(progname, mnt, lazy);
|
||||
else {
|
||||
res = umount2(mnt, lazy ? 2 : 0);
|
||||
if (res == -1 && !quiet)
|
||||
fprintf(stderr, "%s: failed to unmount %s: %s\n", progname,
|
||||
mnt, strerror(errno));
|
||||
}
|
||||
|
||||
if (drop_privs())
|
||||
res = -1;
|
||||
|
||||
} else
|
||||
res = mount_fuse(mnt, opts);
|
||||
|
||||
out:
|
||||
umask(old_umask);
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -189,114 +189,9 @@ void fuse_kern_unmount(const char *mountpoint, int fd)
|
|||
}
|
||||
close(fd);
|
||||
|
||||
if (geteuid() == 0) {
|
||||
fuse_mnt_umount("fuse", mountpoint, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
res = umount2(mountpoint, 2);
|
||||
if (res == 0)
|
||||
return;
|
||||
|
||||
fusermount(1, 0, 1, "", mountpoint);
|
||||
}
|
||||
|
||||
static int fuse_mount_sys(const char *mnt, struct mount_opts *mo,
|
||||
const char *mnt_opts)
|
||||
{
|
||||
char tmp[128];
|
||||
const char *devname = "/dev/fuse";
|
||||
char *source = NULL;
|
||||
char *type = NULL;
|
||||
struct stat stbuf;
|
||||
int fd;
|
||||
int res;
|
||||
|
||||
if (!mnt) {
|
||||
fprintf(stderr, "fuse: missing mountpoint\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = lstat(mnt, &stbuf);
|
||||
if (res == -1) {
|
||||
fprintf(stderr ,"fuse: failed to access mountpoint %s: %s\n",
|
||||
mnt, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fd = open(devname, O_RDWR);
|
||||
if (fd == -1) {
|
||||
if (errno == ENODEV || errno == ENOENT)
|
||||
fprintf(stderr,
|
||||
"fuse: device not found, try 'modprobe fuse' first\n");
|
||||
else
|
||||
fprintf(stderr, "fuse: failed to open %s: %s\n", devname,
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(tmp, sizeof(tmp), "fd=%i,rootmode=%o,user_id=%i,group_id=%i", fd,
|
||||
stbuf.st_mode & S_IFMT, getuid(), getgid());
|
||||
|
||||
res = fuse_opt_add_opt(&mo->kernel_opts, tmp);
|
||||
if (res == -1)
|
||||
goto out_close;
|
||||
|
||||
source = malloc((mo->fsname ? strlen(mo->fsname) : 0) +
|
||||
strlen(devname) + 32);
|
||||
|
||||
type = malloc(32);
|
||||
if (!type || !source) {
|
||||
fprintf(stderr, "fuse: failed to allocate memory\n");
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
strcpy(type, mo->blkdev ? "fuseblk" : "fuse");
|
||||
strcpy(source, mo->fsname ? mo->fsname : devname);
|
||||
|
||||
res = mount(source, mnt, type, mo->flags, mo->kernel_opts);
|
||||
if (res == -1) {
|
||||
/*
|
||||
* Maybe kernel doesn't support unprivileged mounts, in this
|
||||
* case try falling back to fusermount
|
||||
*/
|
||||
if (errno == EPERM) {
|
||||
res = -2;
|
||||
} else {
|
||||
int errno_save = errno;
|
||||
if (mo->blkdev && errno == ENODEV && !fuse_mnt_check_fuseblk())
|
||||
fprintf(stderr, "fuse: 'fuseblk' support missing\n");
|
||||
else
|
||||
fprintf(stderr, "fuse: mount failed: %s\n",
|
||||
strerror(errno_save));
|
||||
}
|
||||
|
||||
goto out_close;
|
||||
}
|
||||
|
||||
if (geteuid() == 0) {
|
||||
char *newmnt = fuse_mnt_resolve_path("fuse", mnt);
|
||||
res = -1;
|
||||
if (!newmnt)
|
||||
goto out_umount;
|
||||
|
||||
res = fuse_mnt_add_mount("fuse", source, newmnt, type, mnt_opts);
|
||||
free(newmnt);
|
||||
if (res == -1)
|
||||
goto out_umount;
|
||||
}
|
||||
|
||||
return fd;
|
||||
|
||||
out_umount:
|
||||
umount2(mnt, 2); /* lazy umount */
|
||||
out_close:
|
||||
free(type);
|
||||
free(source);
|
||||
close(fd);
|
||||
return res;
|
||||
}
|
||||
|
||||
static int get_mnt_flag_opts(char **mnt_optsp, int flags)
|
||||
{
|
||||
int i;
|
||||
|
@ -340,16 +235,12 @@ int fuse_kern_mount(const char *mountpoint, struct fuse_args *args)
|
|||
goto out;
|
||||
if (mo.mtab_opts && fuse_opt_add_opt(&mnt_opts, mo.mtab_opts) == -1)
|
||||
goto out;
|
||||
if (mo.fusermount_opts && fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) < 0)
|
||||
goto out;
|
||||
|
||||
res = fuse_mount_sys(mountpoint, &mo, mnt_opts);
|
||||
if (res == -2) {
|
||||
if (mo.fusermount_opts &&
|
||||
fuse_opt_add_opt(&mnt_opts, mo.fusermount_opts) == -1)
|
||||
goto out;
|
||||
|
||||
res = fusermount(0, 0, 0, mnt_opts ? mnt_opts : "", mountpoint);
|
||||
}
|
||||
out:
|
||||
res = fusermount(0, 0, 0, mnt_opts ? mnt_opts : "", mountpoint);
|
||||
|
||||
out:
|
||||
free(mnt_opts);
|
||||
free(mo.fsname);
|
||||
free(mo.fusermount_opts);
|
||||
|
|
|
@ -167,6 +167,24 @@ static const char *usage_msg =
|
|||
"\n"
|
||||
"%s";
|
||||
|
||||
#ifdef FUSE_INTERNAL
|
||||
int drop_privs(void);
|
||||
int restore_privs(void);
|
||||
#else
|
||||
/*
|
||||
* setuid and setgid root ntfs-3g denies to start with external FUSE,
|
||||
* therefore the below functions are no-op in such case.
|
||||
*/
|
||||
static int drop_privs(void) { return 0; }
|
||||
static int restore_privs(void) { return 0; }
|
||||
|
||||
static const char *setuid_msg =
|
||||
"Mount is denied because setuid and setgid root ntfs-3g is insecure with an\n"
|
||||
"external FUSE library. Either remove the setuid/setgid bit from the binary\n"
|
||||
"or rebuild NTFS-3G with integrated FUSE support.\n";
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* ntfs_fuse_is_named_data_stream - check path to be to named data stream
|
||||
* @path: path to check
|
||||
|
@ -2143,35 +2161,11 @@ static int set_fuseblk_options(char **parsed_options)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifndef FUSE_INTERNAL
|
||||
static int set_uid(uid_t uid)
|
||||
{
|
||||
if (setuid(uid)) {
|
||||
ntfs_log_perror("Failed to set uid to %d", uid);
|
||||
return NTFS_VOLUME_NO_PRIVILEGE;
|
||||
}
|
||||
return NTFS_VOLUME_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct fuse *mount_fuse(char *parsed_options)
|
||||
{
|
||||
struct fuse *fh = NULL;
|
||||
struct fuse_args args = FUSE_ARGS_INIT(0, NULL);
|
||||
#ifndef FUSE_INTERNAL
|
||||
uid_t uid, euid;
|
||||
|
||||
/*
|
||||
* We must raise privilege if possible, otherwise the user[s] fstab
|
||||
* option doesn't work because mount(8) always drops privilege what
|
||||
* the blkdev option requires.
|
||||
*/
|
||||
uid = getuid();
|
||||
euid = geteuid();
|
||||
|
||||
if (set_uid(euid))
|
||||
return NULL;
|
||||
#endif
|
||||
/* Libfuse can't always find fusermount, so let's help it. */
|
||||
if (setenv("PATH", ":/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin", 0))
|
||||
ntfs_log_perror("WARNING: Failed to set $PATH\n");
|
||||
|
@ -2194,15 +2188,6 @@ static struct fuse *mount_fuse(char *parsed_options)
|
|||
|
||||
if (fuse_set_signal_handlers(fuse_get_session(fh)))
|
||||
goto err_destory;
|
||||
/*
|
||||
* We can't drop privilege if internal FUSE is used because internal
|
||||
* unmount needs it. Kernel 2.6.25 may include unprivileged full
|
||||
* mount/unmount support.
|
||||
*/
|
||||
#ifndef FUSE_INTERNAL
|
||||
if (set_uid(uid))
|
||||
goto err_destory;
|
||||
#endif
|
||||
out:
|
||||
fuse_opt_free_args(&args);
|
||||
return fh;
|
||||
|
@ -2245,6 +2230,15 @@ int main(int argc, char *argv[])
|
|||
struct stat sbuf;
|
||||
int err;
|
||||
|
||||
#ifndef FUSE_INTERNAL
|
||||
if ((getuid() != geteuid()) || (getgid() != getegid())) {
|
||||
fprintf(stderr, "%s", setuid_msg);
|
||||
return NTFS_VOLUME_INSECURE;
|
||||
}
|
||||
#endif
|
||||
if (drop_privs())
|
||||
return NTFS_VOLUME_NO_PRIVILEGE;
|
||||
|
||||
utils_set_locale();
|
||||
ntfs_log_set_handler(ntfs_log_handler_stderr);
|
||||
|
||||
|
@ -2264,10 +2258,18 @@ int main(int argc, char *argv[])
|
|||
|
||||
#if defined(linux) || defined(__uClinux__)
|
||||
fstype = get_fuse_fstype();
|
||||
|
||||
err = NTFS_VOLUME_NO_PRIVILEGE;
|
||||
if (restore_privs())
|
||||
goto err_out;
|
||||
|
||||
if (fstype == FSTYPE_NONE || fstype == FSTYPE_UNKNOWN)
|
||||
fstype = load_fuse_module();
|
||||
|
||||
create_dev_fuse();
|
||||
|
||||
if (drop_privs())
|
||||
goto err_out;
|
||||
#endif
|
||||
|
||||
if (stat(opts.device, &sbuf)) {
|
||||
|
|
|
@ -64,6 +64,8 @@ Not enough privilege to mount.
|
|||
Out of memory.
|
||||
.IP 21
|
||||
Unclassified FUSE error.
|
||||
.IP 22
|
||||
Security hazard. Execution is denied because it would be exploitable.
|
||||
.SH KNOWN ISSUES
|
||||
Please see
|
||||
.RS
|
||||
|
|
|
@ -90,9 +90,9 @@ static const char *fakeraid_msg =
|
|||
"to mount NTFS. Please see the 'dmraid' documentation for help.\n";
|
||||
|
||||
static const char *access_denied_msg =
|
||||
"Please check the device and the ntfs-3g binary permissions, the mounting\n"
|
||||
"user and group ID, and the mount options. You can find more explanation\n"
|
||||
"at http://ntfs-3g.org/support.html#useroption\n";
|
||||
"Please check the volume and the ntfs-3g binary permissions,\n"
|
||||
"and the mounting user ID. More explanation is provided at\n"
|
||||
"http://ntfs-3g.org/support.html#unprivileged\n";
|
||||
|
||||
static const char *forced_mount_msg =
|
||||
"\n"
|
||||
|
|
Loading…
Reference in New Issue