558 lines
14 KiB
Diff
558 lines
14 KiB
Diff
From: Dan Rosenberg <dan.j.rosenberg () gmail com>
|
|
Date: Fri, 5 Mar 2010 12:06:01 -0500
|
|
|
|
============================================
|
|
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.rosenberg () 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.
|
|
|
|
http://seclists.org/fulldisclosure/2010/Mar/122
|
|
|
|
|
|
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) {
|