============================================ ncpfs, Multiple Vulnerabilities March 5, 2010 CVE-2010-0788, CVE-2010-0790, CVE-2010-0791 ============================================
==Description== The ncpmount, ncpumount, and ncplogin utilities, installed as part of the ncpfs package, contain several vulnerabilities. 1. ncpmount, ncpumount, and ncplogin are vulnerable to race conditions that allow a local attacker to unmount arbitrary mountpoints, causing denial-of-service, or mount Netware shares to arbitrary directories, potentially leading to root compromise. This issue was formerly assigned CVE-2009-3297, but has since been re-assigned CVE-2010-0788 to avoid overlap with related bugs in other packages. 2. ncpumount is vulnerable to an information disclosure vulnerability that allows a local attacker to verify the existence of arbitrary files, violating directory permissions. This issue has been assigned CVE-2010-0790. 3. ncpmount, ncpumount, and ncplogin create lockfiles insecurely, allowing a local attacker to leave a stale lockfile at /etc/mtab~, causing other mount utilities to fail and creating denial-of-service conditions. This issue has been assigned CVE-2010-0791. ==Workaround== If unprivileged users do not need the ability to mount and unmount Netware shares, then the suid bit should be removed from these utilities. ==Solution== A patch has been released that resolves these issues (attached to this advisory). ncpfs-2.2.6.partial.patch is intended for ncpfs releases that have already been patched against the first vulnerability in this report (CVE-2010-0788, formerly CVE-2009-3297). It has been tested against the latest ncpfs packages distributed by Fedora, Red Hat, and Mandriva. ncpfs-2.2.6.full.patch is intended for ncpfs releases that have not been patched against any of these vulnerabilities. It has been tested against the latest ncpfs packages distributed by Debian, Ubuntu, and the upstream release (ftp://platan.vc.cvut.cz/pub/linux/ncpfs/). Users are advised to recompile from source, or request updated packages from downstream distributors. ==Credits== These vulnerabilities were discovered by Dan Rosenberg (dan.j.rosenb...@gmail.com). Thanks to Vitezslav Crhonek for the patch against the first issue. ==References== CVE identifiers CVE-2010-0788, CVE-2010-0790, and CVE-2010-0791 have been assigned to these issues.
diff -ur ncpfs-2.2.6.orig/sutil/ncplogin.c ncpfs-2.2.6/sutil/ncplogin.c --- ncpfs-2.2.6.orig/sutil/ncplogin.c 2010-03-03 16:18:59.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncplogin.c 2010-03-03 16:17:41.000000000 -0500 @@ -934,7 +934,9 @@ NWDSFreeContext(ctx); /* ncpmap, ncplogin must write in /etc/mtab */ { + block_sigs(); add_mnt_entry(mount_name, mount_point, info.flags); + unblock_sigs(); } free(mount_name); if (info.echo_mnt_pnt) { diff -ur ncpfs-2.2.6.orig/sutil/ncpm_common.c ncpfs-2.2.6/sutil/ncpm_common.c --- ncpfs-2.2.6.orig/sutil/ncpm_common.c 2010-03-03 16:18:59.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpm_common.c 2010-03-03 16:17:41.000000000 -0500 @@ -360,7 +360,7 @@ #endif static inline int ncpm_suser(void) { - return setreuid(-1, 0); + return setresuid(0, 0, myuid); } static int ncpm_normal(void) { @@ -368,11 +368,31 @@ int v; e = errno; - v = setreuid(-1, myuid); + v = setresuid(myuid, myuid, 0); errno = e; return v; } +void block_sigs(void) { + + sigset_t mask, orig_mask; + sigfillset(&mask); + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + errexit(-1, _("Blocking signals failed.\n")); + } +} + +void unblock_sigs(void) { + + sigset_t mask, orig_mask; + sigemptyset(&mask); + + if (sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + errexit(-1, _("Un-blocking signals failed.\n")); + } +} + static int proc_ncpm_mount(const char* source, const char* target, const char* filesystem, unsigned long mountflags, const void* data) { int v; int e; @@ -444,7 +464,7 @@ } datav2.file_mode = data->file_mode; datav2.dir_mode = data->dir_mode; - err = proc_ncpm_mount(mount_name, data->mount_point, "ncpfs", flags, (void*) &datav2); + err = proc_ncpm_mount(mount_name, ".", "ncpfs", flags, (void*) &datav2); if (err) return errno; return 0; @@ -508,7 +528,7 @@ exit(0); /* Should not return from process_connection */ } close(pp[0]); - err=proc_ncpm_mount(mount_name, data->mount_point, "ncpfs", flags, (void*) &datav3); + err=proc_ncpm_mount(mount_name, ".", "ncpfs", flags, (void*) &datav3); if (err) { err = errno; /* Mount unsuccesful so we have to kill daemon */ @@ -559,7 +579,7 @@ sprintf(mountopts, "version=%u,flags=%u,owner=%u,uid=%u,gid=%u,mode=%u,dirmode=%u,timeout=%u,retry=%u,wdogpid=%u,ncpfd=%u,infofd=%u", NCP_MOUNT_VERSION_V5, ncpflags, data->mounted_uid, data->uid, data->gid, data->file_mode, data->dir_mode, data->time_out, data->retry_count, wdog_pid, data->ncp_fd, pp[1]); - err=proc_ncpm_mount(mount_name, data->mount_point, "ncpfs", flags, mountopts); + err=proc_ncpm_mount(mount_name, ".", "ncpfs", flags, mountopts); } else { err=-1; } @@ -577,7 +597,7 @@ datav4.file_mode = data->file_mode; datav4.dir_mode = data->dir_mode; datav4.wdog_pid = wdog_pid; - err = proc_ncpm_mount(mount_name, data->mount_point, "ncpfs", flags, (void*)&datav4); + err = proc_ncpm_mount(mount_name, ".", "ncpfs", flags, (void*)&datav4); if (err) { err = errno; /* Mount unsuccesful so we have to kill daemon */ @@ -1395,6 +1415,17 @@ } #endif /* MOUNT3 */ +static int check_name(const char *name) +{ + char *s; + for (s = "\n\t\\"; *s; s++) { + if (strchr(name, *s)) { + return -1; + } + } + return 0; +} + static const struct smntflags { unsigned int flag; const char* name; @@ -1416,6 +1447,9 @@ int fd; FILE* mtab; + if (check_name(mount_name) == -1 || check_name(mpnt) == -1) + errexit(107, _("Illegal character in mount entry\n")); + ment.mnt_fsname = mount_name; ment.mnt_dir = mpnt; ment.mnt_type = (char*)"ncpfs"; diff -ur ncpfs-2.2.6.orig/sutil/ncpm_common.h ncpfs-2.2.6/sutil/ncpm_common.h --- ncpfs-2.2.6.orig/sutil/ncpm_common.h 2010-03-03 16:18:59.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpm_common.h 2010-03-03 16:17:41.000000000 -0500 @@ -121,6 +121,9 @@ int proc_aftermount(const struct ncp_mount_info* info, NWCONN_HANDLE* conn); int proc_ncpm_umount(const char* dir); +void block_sigs(void); +void unblock_sigs(void); + #define UNUSED(x) x __attribute__((unused)) #endif /* __NCPM_COMMON_H__ */ diff -ur ncpfs-2.2.6.orig/sutil/ncpmount.c ncpfs-2.2.6/sutil/ncpmount.c --- ncpfs-2.2.6.orig/sutil/ncpmount.c 2010-03-03 16:18:59.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpmount.c 2010-03-03 16:17:41.000000000 -0500 @@ -359,11 +359,17 @@ usage(); return -1; } + realpath(argv[optind], mount_point); - if (stat(mount_point, &st) == -1) + if (chdir(mount_point)) + { + errexit(31, _("Could not change directory into mount target %s: %s\n"), + mount_point, strerror(errno)); + } + if (stat(".", &st) == -1) { - errexit(31, _("Could not find mount point %s: %s\n"), + errexit(31, _("Mount point %s does not exist: %s\n"), mount_point, strerror(errno)); } if (mount_ok(&st) != 0) @@ -714,7 +720,9 @@ ncp_close(conn); if (!opt_n) { + block_sigs(); add_mnt_entry(mount_name, mount_point, info.flags); + unblock_sigs(); } return 0; } diff -ur ncpfs-2.2.6.orig/sutil/ncpumount.c ncpfs-2.2.6/sutil/ncpumount.c --- ncpfs-2.2.6.orig/sutil/ncpumount.c 2010-03-03 16:18:59.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpumount.c 2010-03-03 16:17:41.000000000 -0500 @@ -70,13 +70,24 @@ #include <mntent.h> #include <pwd.h> +#include <sched.h> + #include "private/libintl.h" #define _(X) X +#ifndef MS_REC +#define MS_REC 16384 +#endif +#ifndef MS_SLAVE +#define MS_SLAVE (1<<19) +#endif + static char *progname; static int is_ncplogout = 0; +uid_t uid; + static void usage(void) { @@ -117,6 +128,40 @@ va_end(ap); } +/* Mostly copied from ncpm_common.c */ +void block_sigs(void) { + + sigset_t mask, orig_mask; + sigfillset(&mask); + sigdelset(&mask, SIGALRM); /* Need SIGALRM for ncpumount */ + + if(setresuid(0, 0, uid) < 0) { + eprintf("Failed to raise privileges.\n"); + exit(-1); + } + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + eprintf("Blocking signals failed.\n"); + exit(-1); + } +} + +void unblock_sigs(void) { + + sigset_t mask, orig_mask; + sigemptyset(&mask); + + if(setresuid(uid, uid, 0) < 0) { + eprintf("Failed to drop privileges.\n"); + exit(-1); + } + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + eprintf("Un-blocking signals failed.\n"); + exit(-1); + } +} + static void alarmSignal(int sig) { (void)sig; } @@ -192,10 +237,13 @@ if (!numEntries) return 0; /* don't waste time ! */ + block_sigs(); + while ((fd = open(MOUNTED "~", O_RDWR | O_CREAT | O_EXCL, 0600)) == -1) { struct timespec tm; if (errno != EEXIST || retries == 0) { + unblock_sigs(); eprintf(_("Can't get %s~ lock file: %s\n"), MOUNTED, strerror(errno)); return 1; } @@ -206,6 +254,7 @@ alarm(0); close(fd); if (err) { + unblock_sigs(); eprintf(_("Can't lock lock file %s~: %s\n"), MOUNTED, _("Lock timed out")); return 1; } @@ -223,26 +272,205 @@ err = __clearMtab(mount_points, numEntries); if ((unlink(MOUNTED "~") == -1) && (err == 0)){ + unblock_sigs(); eprintf(_("Can't remove %s~"), MOUNTED); return 1; } + unblock_sigs(); return err; } + +int ncp_mnt_umount(const char *abs_mnt, const char *rel_mnt) +{ + if (umount(rel_mnt) != 0) { + eprintf(_("Could not umount %s: %s\n"), + abs_mnt, strerror(errno)); + return -1; + } + return 0; +} + + +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) { + eprintf(_("Failed to mark mounts slave: %s\n"), + strerror(errno)); + return 1; + } + + res = mount(".", "/tmp", "", MS_BIND | MS_REC, NULL); + if (res == -1) { + eprintf(_("Failed to bind parent to /tmp: %s\n"), + strerror(errno)); + return 1; + } + + fp = setmntent(procmounts, "r"); + if (fp == NULL) { + eprintf(_("Failed to open %s: %s\n"), + 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) { + eprintf(_("%s not mounted\n"), mnt); + return 1; + } + + return 0; +} + + +static int check_is_mount(const char *last, const char *mnt) +{ + char buf[131072]; + pid_t pid, p; + int status; + const char *a[2] = { last, mnt }; + + pid = clone(check_is_mount_child, buf + 65536, CLONE_NEWNS, (void *) a); + if (pid == (pid_t) -1) { + eprintf(_("Failed to clone namespace: %s\n"), + strerror(errno)); + return -1; + } + p = waitpid(pid, &status, __WCLONE); + if (p == (pid_t) -1) { + eprintf(_("Waitpid failed: %s\n"), + strerror(errno)); + return -1; + } + if (!WIFEXITED(status)) { + eprintf(_("Child terminated abnormally (status %i)\n"), + 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[PATH_MAX]; + int res; + + tmp = strrchr(copy, '/'); + if (tmp == NULL || tmp[1] == '\0') { + eprintf(_("Internal error: invalid abs path: <%s>\n"), + 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) { + eprintf(_("Failed to open current directory: %s\n"), + strerror(errno)); + return -1; + } + res = chdir(parent); + if (res == -1) { + eprintf(_("Failed to chdir to %s: %s\n"), + parent, strerror(errno)); + return -1; + } + if (getcwd(buf, sizeof(buf)) == NULL) { + eprintf(_("Failed to obtain current directory: %s\n"), + strerror(errno)); + return -1; + } + if (strcmp(buf, parent) != 0) { + eprintf(_("Mountpoint moved (%s -> %s)\n"), + parent, buf); + return -1; + + } + + return 0; +} + + +static int unmount_ncp(const char *mount_point) +{ + int currdir_fd = -1; + char *copy; + const char *last; + int res; + + copy = strdup(mount_point); + if (copy == NULL) { + eprintf(_("Failed to allocate memory\n")); + return -1; + } + res = chdir_to_parent(copy, &last, &currdir_fd); + if (res == -1) + goto out; + res = check_is_mount(last, mount_point); + if (res == -1) + goto out; + res = ncp_mnt_umount(mount_point, last); + +out: + free(copy); + if (currdir_fd != -1) { + fchdir(currdir_fd); + close(currdir_fd); + } + + return res; +} + static int do_umount(const char *mount_point) { int fid = open(mount_point, O_RDONLY, 0); uid_t mount_uid; + int res; if (fid == -1) { - eprintf(_("Could not open %s: %s\n"), - mount_point, strerror(errno)); + eprintf(_("Invalid or unauthorized mountpoint %s\n"), + mount_point); return -1; } if (ncp_get_mount_uid(fid, &mount_uid) != 0) { close(fid); - eprintf(_("%s probably not ncp-filesystem\n"), + eprintf(_("Invalid or unauthorized mountpoint %s\n"), mount_point); return -1; } @@ -253,12 +481,8 @@ return -1; } close(fid); - if (umount(mount_point) != 0) { - eprintf(_("Could not umount %s: %s\n"), - mount_point, strerror(errno)); - return -1; - } - return 0; + res = unmount_ncp(mount_point); + return res; } @@ -409,7 +633,8 @@ int allConns = 0; const char *serverName = NULL; const char *treeName = NULL; - uid_t uid = getuid(); + + uid = getuid(); progname = strrchr(argv[0], '/'); if (progname) {
diff -ur ncpfs-2.2.6.orig/sutil/ncplogin.c ncpfs-2.2.6/sutil/ncplogin.c --- ncpfs-2.2.6.orig/sutil/ncplogin.c 2010-03-03 16:13:45.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncplogin.c 2010-03-03 16:17:41.000000000 -0500 @@ -934,7 +934,9 @@ NWDSFreeContext(ctx); /* ncpmap, ncplogin must write in /etc/mtab */ { + block_sigs(); add_mnt_entry(mount_name, mount_point, info.flags); + unblock_sigs(); } free(mount_name); if (info.echo_mnt_pnt) { diff -ur ncpfs-2.2.6.orig/sutil/ncpm_common.c ncpfs-2.2.6/sutil/ncpm_common.c --- ncpfs-2.2.6.orig/sutil/ncpm_common.c 2010-03-03 16:13:45.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpm_common.c 2010-03-03 16:17:41.000000000 -0500 @@ -360,7 +360,7 @@ #endif static inline int ncpm_suser(void) { - return setreuid(-1, 0); + return setresuid(0, 0, myuid); } static int ncpm_normal(void) { @@ -368,11 +368,31 @@ int v; e = errno; - v = setreuid(-1, myuid); + v = setresuid(myuid, myuid, 0); errno = e; return v; } +void block_sigs(void) { + + sigset_t mask, orig_mask; + sigfillset(&mask); + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + errexit(-1, _("Blocking signals failed.\n")); + } +} + +void unblock_sigs(void) { + + sigset_t mask, orig_mask; + sigemptyset(&mask); + + if (sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + errexit(-1, _("Un-blocking signals failed.\n")); + } +} + static int proc_ncpm_mount(const char* source, const char* target, const char* filesystem, unsigned long mountflags, const void* data) { int v; int e; diff -ur ncpfs-2.2.6.orig/sutil/ncpm_common.h ncpfs-2.2.6/sutil/ncpm_common.h --- ncpfs-2.2.6.orig/sutil/ncpm_common.h 2010-03-03 16:13:45.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpm_common.h 2010-03-03 16:17:41.000000000 -0500 @@ -121,6 +121,9 @@ int proc_aftermount(const struct ncp_mount_info* info, NWCONN_HANDLE* conn); int proc_ncpm_umount(const char* dir); +void block_sigs(void); +void unblock_sigs(void); + #define UNUSED(x) x __attribute__((unused)) #endif /* __NCPM_COMMON_H__ */ diff -ur ncpfs-2.2.6.orig/sutil/ncpmount.c ncpfs-2.2.6/sutil/ncpmount.c --- ncpfs-2.2.6.orig/sutil/ncpmount.c 2010-03-03 16:13:45.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpmount.c 2010-03-03 16:17:41.000000000 -0500 @@ -720,7 +720,9 @@ ncp_close(conn); if (!opt_n) { + block_sigs(); add_mnt_entry(mount_name, mount_point, info.flags); + unblock_sigs(); } return 0; } diff -ur ncpfs-2.2.6.orig/sutil/ncpumount.c ncpfs-2.2.6/sutil/ncpumount.c --- ncpfs-2.2.6.orig/sutil/ncpumount.c 2010-03-03 16:13:45.000000000 -0500 +++ ncpfs-2.2.6/sutil/ncpumount.c 2010-03-03 16:17:41.000000000 -0500 @@ -86,6 +86,8 @@ static char *progname; static int is_ncplogout = 0; +uid_t uid; + static void usage(void) { @@ -126,6 +128,40 @@ va_end(ap); } +/* Mostly copied from ncpm_common.c */ +void block_sigs(void) { + + sigset_t mask, orig_mask; + sigfillset(&mask); + sigdelset(&mask, SIGALRM); /* Need SIGALRM for ncpumount */ + + if(setresuid(0, 0, uid) < 0) { + eprintf("Failed to raise privileges.\n"); + exit(-1); + } + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + eprintf("Blocking signals failed.\n"); + exit(-1); + } +} + +void unblock_sigs(void) { + + sigset_t mask, orig_mask; + sigemptyset(&mask); + + if(setresuid(uid, uid, 0) < 0) { + eprintf("Failed to drop privileges.\n"); + exit(-1); + } + + if(sigprocmask(SIG_SETMASK, &mask, &orig_mask) < 0) { + eprintf("Un-blocking signals failed.\n"); + exit(-1); + } +} + static void alarmSignal(int sig) { (void)sig; } @@ -201,10 +237,13 @@ if (!numEntries) return 0; /* don't waste time ! */ + block_sigs(); + while ((fd = open(MOUNTED "~", O_RDWR | O_CREAT | O_EXCL, 0600)) == -1) { struct timespec tm; if (errno != EEXIST || retries == 0) { + unblock_sigs(); eprintf(_("Can't get %s~ lock file: %s\n"), MOUNTED, strerror(errno)); return 1; } @@ -215,6 +254,7 @@ alarm(0); close(fd); if (err) { + unblock_sigs(); eprintf(_("Can't lock lock file %s~: %s\n"), MOUNTED, _("Lock timed out")); return 1; } @@ -232,9 +272,11 @@ err = __clearMtab(mount_points, numEntries); if ((unlink(MOUNTED "~") == -1) && (err == 0)){ + unblock_sigs(); eprintf(_("Can't remove %s~"), MOUNTED); return 1; } + unblock_sigs(); return err; } @@ -422,13 +464,13 @@ int res; if (fid == -1) { - eprintf(_("Could not open %s: %s\n"), - mount_point, strerror(errno)); + eprintf(_("Invalid or unauthorized mountpoint %s\n"), + mount_point); return -1; } if (ncp_get_mount_uid(fid, &mount_uid) != 0) { close(fid); - eprintf(_("%s probably not ncp-filesystem\n"), + eprintf(_("Invalid or unauthorized mountpoint %s\n"), mount_point); return -1; } @@ -591,7 +633,8 @@ int allConns = 0; const char *serverName = NULL; const char *treeName = NULL; - uid_t uid = getuid(); + + uid = getuid(); progname = strrchr(argv[0], '/'); if (progname) {
_______________________________________________ Full-Disclosure - We believe in it. Charter: http://lists.grok.org.uk/full-disclosure-charter.html Hosted and sponsored by Secunia - http://secunia.com/