From fca849d3656c60bd1f2609c6d76c5c2b9cbea629 Mon Sep 17 00:00:00 2001 From: szaka Date: Sat, 16 Feb 2008 14:25:23 +0000 Subject: [PATCH] fix unprivileged mount/unmount --- include/ntfs-3g/volume.h | 3 +- libfuse-lite/fusermount.c | 220 ++++++++++++++++++++------------------ libfuse-lite/mount.c | 119 +-------------------- src/ntfs-3g.c | 68 ++++++------ src/ntfs-3g.probe.8.in | 2 + src/utils.c | 6 +- 6 files changed, 163 insertions(+), 255 deletions(-) diff --git a/include/ntfs-3g/volume.h b/include/ntfs-3g/volume.h index fc2973aa..67ca94f6 100644 --- a/include/ntfs-3g/volume.h +++ b/include/ntfs-3g/volume.h @@ -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; /** diff --git a/libfuse-lite/fusermount.c b/libfuse-lite/fusermount.c index bd80c1cc..1bede1b5 100644 --- a/libfuse-lite/fusermount.c +++ b/libfuse-lite/fusermount.c @@ -26,6 +26,7 @@ #include #include #include +#include #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; } diff --git a/libfuse-lite/mount.c b/libfuse-lite/mount.c index 52cf96bb..712cdb53 100644 --- a/libfuse-lite/mount.c +++ b/libfuse-lite/mount.c @@ -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); diff --git a/src/ntfs-3g.c b/src/ntfs-3g.c index dab35970..7fa73bd9 100644 --- a/src/ntfs-3g.c +++ b/src/ntfs-3g.c @@ -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)) { diff --git a/src/ntfs-3g.probe.8.in b/src/ntfs-3g.probe.8.in index 73198ef5..9ba4da6d 100644 --- a/src/ntfs-3g.probe.8.in +++ b/src/ntfs-3g.probe.8.in @@ -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 diff --git a/src/utils.c b/src/utils.c index 9fe1aa64..b57b0714 100644 --- a/src/utils.c +++ b/src/utils.c @@ -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"