fix unprivileged mount/unmount

master
szaka 2008-02-16 14:25:23 +00:00
parent 6b52c58799
commit fca849d365
6 changed files with 163 additions and 255 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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