http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=572937 http://seclists.org/fulldisclosure/2010/Mar/122 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. --- a/sutil/ncplogin.c 2010-03-03 16:18:59.000000000 -0500 +++ b/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) { --- a/sutil/ncpm_common.c 2010-03-03 16:18:59.000000000 -0500 +++ b/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"; --- a/sutil/ncpm_common.h 2010-03-03 16:18:59.000000000 -0500 +++ b/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__ */ --- a/sutil/ncpmount.c 2010-03-03 16:18:59.000000000 -0500 +++ b/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; } --- a/sutil/ncpumount.c 2010-03-03 16:18:59.000000000 -0500 +++ b/sutil/ncpumount.c 2010-03-03 16:17:41.000000000 -0500 @@ -70,13 +70,24 @@ #include #include +#include + #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) {