From 8b3a0c74a15e237eb4b7053774600f0ce3fff403 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Tue, 26 Jan 2010 18:20:13 +0000 Subject: * Fix race if two "fusermount -u" instances are run in parallel. Reported by Dan Rosenberg * Make sure that the path to be unmounted doesn't refer to a symlink --- ChangeLog | 8 ++ lib/mount.c | 2 +- lib/mount_util.c | 31 +++-- lib/mount_util.h | 3 +- util/fusermount.c | 380 ++++++++++++++++++++++++++++++++++++++++++++++-------- 5 files changed, 351 insertions(+), 73 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1c197e0..60c52dd 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2010-01-21 Miklos Szeredi + + * Fix race if two "fusermount -u" instances are run in parallel. + Reported by Dan Rosenberg + + * Make sure that the path to be unmounted doesn't refer to a + symlink + 2010-01-14 Miklos Szeredi * Fix compile error on FreeBSD. Patch by Jay Sullivan diff --git a/lib/mount.c b/lib/mount.c index 941644f..c3b16a4 100644 --- a/lib/mount.c +++ b/lib/mount.c @@ -290,7 +290,7 @@ void fuse_kern_unmount(const char *mountpoint, int fd) } if (geteuid() == 0) { - fuse_mnt_umount("fuse", mountpoint, 1); + fuse_mnt_umount("fuse", mountpoint, mountpoint, 1); return; } diff --git a/lib/mount_util.c b/lib/mount_util.c index 3b6e771..e18e7a4 100644 --- a/lib/mount_util.c +++ b/lib/mount_util.c @@ -184,13 +184,13 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, return res; } -static int exec_umount(const char *progname, const char *mnt, int lazy, - int legacy) +static int exec_umount(const char *progname, const char *rel_mnt, int lazy) { int res; int status; sigset_t blockmask; sigset_t oldmask; + int legacy = 0; sigemptyset(&blockmask); sigaddset(&blockmask, SIGCHLD); @@ -200,6 +200,7 @@ static int exec_umount(const char *progname, const char *mnt, int lazy, return -1; } +retry_umount: res = fork(); if (res == -1) { fprintf(stderr, "%s: fork: %s\n", progname, strerror(errno)); @@ -219,11 +220,11 @@ static int exec_umount(const char *progname, const char *mnt, int lazy, sigprocmask(SIG_SETMASK, &oldmask, NULL); setuid(geteuid()); if (legacy) { - execl("/bin/umount", "/bin/umount", "-i", mnt, + execl("/bin/umount", "/bin/umount", "-i", rel_mnt, lazy ? "-l" : NULL, NULL); } else { execl("/bin/umount", "/bin/umount", "--no-canonicalize", - "-i", mnt, lazy ? "-l" : NULL, NULL); + "-i", rel_mnt, lazy ? "-l" : NULL, NULL); } fprintf(stderr, "%s: failed to execute /bin/umount: %s\n", progname, strerror(errno)); @@ -233,8 +234,13 @@ static int exec_umount(const char *progname, const char *mnt, int lazy, if (res == -1) fprintf(stderr, "%s: waitpid: %s\n", progname, strerror(errno)); - if (status != 0) + if (status != 0) { + if (!legacy) { + legacy = 1; + goto retry_umount; + } res = -1; + } out_restore: sigprocmask(SIG_SETMASK, &oldmask, NULL); @@ -242,23 +248,20 @@ static int exec_umount(const char *progname, const char *mnt, int lazy, } -int fuse_mnt_umount(const char *progname, const char *mnt, int lazy) +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy) { int res; - if (!mtab_needs_update(mnt)) { - res = umount2(mnt, lazy ? 2 : 0); + if (!mtab_needs_update(abs_mnt)) { + res = umount2(rel_mnt, lazy ? 2 : 0); if (res == -1) fprintf(stderr, "%s: failed to unmount %s: %s\n", - progname, mnt, strerror(errno)); + progname, abs_mnt, strerror(errno)); return res; } - res = exec_umount(progname, mnt, lazy, 0); - if (res == -1) - res = exec_umount(progname, mnt, lazy, 1); - - return res; + return exec_umount(progname, rel_mnt, lazy); } char *fuse_mnt_resolve_path(const char *progname, const char *orig) diff --git a/lib/mount_util.h b/lib/mount_util.h index cf54d9d..f392f99 100644 --- a/lib/mount_util.h +++ b/lib/mount_util.h @@ -10,7 +10,8 @@ int fuse_mnt_add_mount(const char *progname, const char *fsname, const char *mnt, const char *type, const char *opts); -int fuse_mnt_umount(const char *progname, const char *mnt, int lazy); +int fuse_mnt_umount(const char *progname, const char *abs_mnt, + const char *rel_mnt, int lazy); char *fuse_mnt_resolve_path(const char *progname, const char *orig); int fuse_mnt_check_empty(const char *progname, const char *mnt, mode_t rootmode, off_t rootsize); diff --git a/util/fusermount.c b/util/fusermount.c index c3ecc86..f0d8732 100644 --- a/util/fusermount.c +++ b/util/fusermount.c @@ -26,6 +26,7 @@ #include #include #include +#include #define FUSE_COMMFD_ENV "_FUSE_COMMFD" @@ -37,6 +38,12 @@ #ifndef MS_DIRSYNC #define MS_DIRSYNC 128 #endif +#ifndef MS_REC +#define MS_REC 16384 +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) +#endif static const char *progname; @@ -74,77 +81,336 @@ static void restore_privs(void) } #ifndef IGNORE_MTAB +/* + * 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) +{ + lockf(mtablock, F_ULOCK, 0); + close(mtablock); +} + static int add_mount(const char *source, const char *mnt, const char *type, const char *opts) { return fuse_mnt_add_mount(progname, source, mnt, type, opts); } -static int unmount_fuse(const char *mnt, int quiet, int lazy) +static int may_unmount(const char *mnt, int quiet) { - 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; + struct mntent *entp; + FILE *fp; + const char *user = NULL; + char uidstr[32]; + unsigned uidlen = 0; + int found; + const char *mtab = _PATH_MOUNTED; - fp = setmntent(mtab, "r"); - if (fp == NULL) { - fprintf(stderr, - "%s: failed to open %s: %s\n", progname, mtab, - strerror(errno)); - return -1; - } + user = get_user_name(); + if (user == NULL) + 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; - } + 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); + } + endmntent(fp); - if (!found) { - if (!quiet) - fprintf(stderr, - "%s: entry for %s not found in %s\n", - progname, mnt, mtab); - return -1; + if (!found) { + if (!quiet) + fprintf(stderr, + "%s: entry for %s not found in %s\n", + progname, mnt, mtab); + return -1; + } + + return 0; +} + +/* + * Check whether the file specified in "fusermount -u" is really a + * mountpoint and not a symlink. This is necessary otherwise the user + * could move the mountpoint away and replace it with a symlink + * pointing to an arbitrary mount, thereby tricking fusermount into + * 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 + * namespace by marking all mounts "slave". + * + * Then bind mount parent onto a stable base where the user can't move + * it around. Use "/tmp", since it will almost certainly exist, but + * anything similar would do as well. + * + * 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 + * it around within the parent directory as rename() will return EBUSY. + */ +static int check_is_mount_child(void *p) +{ + const char **a = p; + const char *last = a[0]; + const char *mnt = a[1]; + int res; + const char *procmounts = "/proc/mounts"; + int found; + FILE *fp; + struct mntent *entp; + + res = mount("", "/", "", MS_SLAVE | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to mark mounts slave: %s\n", + progname, strerror(errno)); + return 1; + } + + res = mount(".", "/tmp", "", MS_BIND | MS_REC, NULL); + if (res == -1) { + fprintf(stderr, "%s: failed to bind parent to /tmp: %s\n", + progname, strerror(errno)); + return 1; + } + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + fprintf(stderr, "%s: failed to open %s: %s\n", progname, + procmounts, strerror(errno)); + return 1; + } + + found = 0; + while ((entp = getmntent(fp)) != NULL) { + if (strncmp(entp->mnt_dir, "/tmp/", 5) == 0 && + strcmp(entp->mnt_dir + 5, last) == 0) { + found = 1; + break; } } + endmntent(fp); + + if (!found) { + fprintf(stderr, "%s: %s not mounted\n", progname, mnt); + return 1; + } + + return 0; +} + +static pid_t clone_newns(void *a) +{ + long long buf[16384]; + size_t stacksize = sizeof(buf) / 2; + char *stack = ((char *) buf) + stacksize; + +#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); + + return __clone2(check_is_mount_child, stack, stacksize, CLONE_NEWNS, a, + NULL, NULL, NULL); +#else + return clone(check_is_mount_child, stack, CLONE_NEWNS, a); +#endif +} + +static int check_is_mount(const char *last, const char *mnt) +{ + pid_t pid, p; + int status; + const char *a[2] = { last, mnt }; + + 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; +} + +static int chdir_to_parent(char *copy, const char **lastp, int *currdir_fd) +{ + 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 = "/"; + } + + *currdir_fd = open(".", O_RDONLY); + if (*currdir_fd == -1) { + fprintf(stderr, + "%s: failed to open current directory: %s\n", + progname, strerror(errno)); + return -1; + } + + 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 fuse_mnt_umount(progname, mnt, lazy); + } + + return 0; +} + +static int unmount_fuse_locked(const char *mnt, int quiet, int lazy) +{ + int currdir_fd = -1; + char *copy; + const char *last; + int res; + + if (getuid() != 0) { + res = may_unmount(mnt, quiet); + if (res == -1) + return -1; + } + + copy = strdup(mnt); + if (copy == NULL) { + fprintf(stderr, "%s: failed to allocate memory\n", progname); + return -1; + } + + res = chdir_to_parent(copy, &last, &currdir_fd); + if (res == -1) + goto out; + + res = check_is_mount(last, mnt); + if (res == -1) + goto out; + + res = fuse_mnt_umount(progname, mnt, last, lazy); + +out: + free(copy); + if (currdir_fd != -1) { + fchdir(currdir_fd); + close(currdir_fd); + } + + return res; +} + +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); + + return res; } static int count_fuse_fs(void) @@ -186,7 +452,7 @@ static int add_mount(const char *source, const char *mnt, const char *type, static int unmount_fuse(const char *mnt, int quiet, int lazy) { - return fuse_mnt_umount(progname, mnt, lazy); + return fuse_mnt_umount(progname, mnt, mnt, lazy); } #endif /* IGNORE_MTAB */ -- cgit v1.2.3