Casinos Not On GamstopNon Gamstop CasinosCasinos Not On GamstopOnline Casinos UKNon Gamstop Casino
4th Aug 1997 [SBWID-192]
COMMAND
	    rfork()
SYSTEMS AFFECTED
	    OpenBSD 2.1, FreeBSD 3.0
PROBLEM
	    Following  info  is  based   on  OpenBSD  Security  Advisory.    A
	    vulnerability in certain 4.4BSD  kernels allows processes to  gain
	    access  to   restricted  resources   by  manipulating   the   file
	    descriptor tables  of SUID  and SGID  executables. Applications of
	    this vulnerability will  allow users to  gain root access.   It is
	    believed  that  all  4.4BSD  operating  systems  implementing  the
	    rfork() system call are currently vulnerable to this problem.
	    Recent 4.4BSD operating systems  added the rfork() system  call as
	    an additional  method of  creating a  new process.  Unlike fork(),
	    rfork() allows the caller tighter control over which resources are
	    shared between  the parent  and child  processes. These  resources
	    include the per-process descriptor table.
	    The descriptor table of a process lists all open file  descriptors
	    for that process. Input and output on files, sockets, and pipes is
	    done through  these descriptors.  Two processes  sharing the  same
	    descriptor table can  read from any  file either has  open in read
	    mode, and write to any file either has in write mode.
	    Unfortunately, the 4.4BSD implementation of rfork() allows this to
	    occur  with  processes  whose  credentials  have  been altered via
	    SUID/SGID programs.  A process can execute any SUID program on the
	    system and gain access to it's file descriptor table. This can  be
	    exploited   to    allow   unprivileged    processes   to    access
	    security-critical resources, such as the password file.
	    The default behavior  of rfork() is  to share the  file descriptor
	    table between the  child and parent  processes. A process  created
	    with rfork()  can therefore,  by default,  be manipulated  by it's
	    parent.
	    An example of  this problem occurs  in passwd(1), an  SUID program
	    that modifies  the password  database. A  user on  the system  can
	    rfork()  a  process  and  use  it  to execute passwd(1). The child
	    process will gain effective  superuser credentials as a  result of
	    executing  the  SUID  program.  The  parent  can then wait for the
	    temporary copy of the password  database to be opened, and  inject
	    a fake entry into it using the file descriptor it now shares  with
	    passwd(1). When the password  database is rebuilt, the  fake entry
	    will be commited to it and system security will be compromised.
	    It  should  be  noted  that  this  is  not  the  only  avenue   of
	    exploitation for this  problem. The vulnerability  allows complete
	    control over  the file  descriptor tables  of privileged programs;
	    this can be exploited in a variety of ways with any SUID program.
	    Another possible attack allows an attacker to, among other things,
	    steal sockets from  network programs; an  attacker can execute  an
	    SUID networking program such  as "ping", duplicate the  descriptor
	    associated with a raw  socket, and close the  original descriptor.
	    The unprivileged attacker now controls a raw socket.
	    Additionally, an attacker can close a descriptor opened by an SUID
	    program,  and  re-open  it  pointing  elsewhere,  causing the SUID
	    program to unwittingly alter any file accessible by the  attacker.
	    Credit goes  to Danny  Dulai (discovery),  Theo de  Raadt (OpenBSD
	    patch) and Tim Newsham (proof-of-concept code).
	    The  following  code  tests  for  the  presence  of  the   rfork()
	    vulnerability on 4.4BSD systems.  If, after running this  program,
	    a file  is created  in "/"  containing the  word "VULNERABLE", the
	    system is vulnerable to the problem.
	    To use this  test, extract the  following two C  programs. Compile
	    the first ("dummy-suid") and make it SUID root, world  executable.
	    Compile and run the second in the same directory.
	
	    -- cut here (dummy-suid.c) --
	    #include <stdio.h>
	    #include <fcntl.h>
	    #include <errno.h>
	    int main() {
		    int fd;
		    umask(2);
		    /* open a file in the root directory */
		    if(fd = open("/VULNERABLE", O_RDWR|O_CREAT) < 0) {
			    perror("open");
			    exit(0);
		    }
		    /* wait for something to happen */
		    for(;;);
		    exit(0);
	    }
	    -- cut here (test.c) --
	    #include <stdio.h>
	    #include <unistd.h>
	    int main() {
		    int p;
		    /* UNPRIVILEGED */
		    /* create a new process that shares it's parent's file
		     * descriptor table
		     */
		    if(!(p = rfork(RFPROC))) {
			    /* wait for parent to open a file, write
			     * to it.
			     */
			    sleep(1);
			    write(3, "VULNERABLE\n", 10);
			    exit(0);
		    }
		    /* PRIVILEGED */
		    /* execute 'p', an SUID program that opens a file and
		     * hangs
		     */
		    execl("./dummy-suid", "dummy-suid", NULL);
		    exit(0);
	    }
	
SOLUTION
	    Provided  at  the   end  of  this   document  is  a   patch   from
	    OpenBSD-current that resolves the problem in OpenBSD systems.  The
	    OpenBSD patch alters execve() to cause it not to honor the SUID or
	    SGID  bit  when  executing  from  a  process  that  shares  a file
	    descriptor table  with a  different process.   Also provided  is a
	    modloadable  workaround  for  FreeBSD.  The  provided  module will
	    disable  the  rfork()  system  call  from  a  running  system that
	    supports loadable modules.
	    OPENBSD PATCH
	    The  following  patch  resolves  the  rfork()  problem  in OpenBSD
	    systems.
	
	    --- kern_exec.c 1997/06/05 08:05:54     1.11
	    +++ kern_exec.c 1997/08/01 22:54:50     1.12
	    @@ -1,4 +1,4 @@
	    -/*     $OpenBSD: kern_exec.c,v 1.11 1997/06/05 08:05:54 deraadt Exp $  */
	    +/*     $OpenBSD: kern_exec.c,v 1.12 1997/08/01 22:54:50 deraadt Exp $  */
	     /*     $NetBSD: kern_exec.c,v 1.75 1996/02/09 18:59:28 christos Exp $  */
	     /*-
	    @@ -124,7 +124,8 @@
			    error = EACCES;
			    goto bad1;
		    }
	    -       if ((vp->v_mount->mnt_flag & MNT_NOSUID) || (p->p_flag & P_TRACED))
	    +       if ((vp->v_mount->mnt_flag & MNT_NOSUID) ||
	    +           (p->p_flag & P_TRACED) || p->p_fd->fd_refcnt > 1)
			    epp->ep_vap->va_mode &= ~(VSUID | VSGID);
		    /* check access.  for root we have to see if any exec bit on */
	
	    FREEBSD WORKAROUND
	    The following module, when  loaded on a FreeBSD  system supporting
	    rfork(), will disable  the system call  as a temporary  resolution
	    to the problem.
	
	    # This is  a shell archive.   Save it in  a file, remove  anything
	    # before  this line,  and then  unpack it  by entering  "sh file".
	    # Note, it may create  directories; files and directories will  be
	    # owned by you and have default permissions.
	    #
	    # This archive contains:
	    #
	    #       Makefile
	    #       unrfork_mod_load.c
	    #
	    echo x - Makefile
	    sed 's/^X//' >Makefile << 'END-of-Makefile'
	    XBINDIR=        .
	    XSRCS=  unrfork_mod_load.c
	    XKMOD=  disable_rfork
	    XNOMAN= none
	    X
	    XCLEANFILES+= ${KMOD}
	    X
	    X.include <bsd.kmod.mk>
	    END-of-Makefile
	    echo x - unrfork_mod_load.c
	    sed 's/^X//' >unrfork_mod_load.c << 'END-of-unrfork_mod_load.c'
	    X#define RFORK_SYSCALL_NO 251
	    X
	    X#include <sys/param.h>
	    X#include <sys/ioctl.h>
	    X#include <sys/proc.h>
	    X#include <sys/systm.h>
	    X#include <sys/sysproto.h>
	    X#include <sys/conf.h>
	    X#include <sys/mount.h>
	    X#include <sys/exec.h>
	    X#include <sys/sysent.h>
	    X#include <sys/lkm.h>
	    X#include <a.out.h>
	    X#include <sys/file.h>
	    X#include <sys/errno.h>
	    X#include <sys/queue.h>
	    X#include <sys/mbuf.h>
	    X#include <sys/socket.h>
	    X#include <sys/socketvar.h>
	    X#include <sys/protosw.h>
	    X#include <sys/kernel.h>
	    X#include <sys/sockio.h>
	    X
	    Xint disable_rfork(struct lkm_table *lkp, int cmd, int ver);
	    X
	    XMOD_MISC(disable_rfork);
	    X
	    Xstatic int
	    Xdisable_rfork_load(struct lkm_table *lkp, int cmd) {
	    X       struct sysent *sp = &sysent[RFORK_SYSCALL_NO];
	    X       int err = 0;
	    X
	    X       switch(cmd) {
	    X               case LKM_E_LOAD:
	    X                       sp->sy_call = (sy_call_t *) nosys;
	    X
	    X                       printf("rfork() call disabled\n");
	    X                       break;
	    X
	    X               case LKM_E_UNLOAD:
	    X                       sp->sy_call = (sy_call_t *) rfork;
	    X
	    X                       printf("rfork() call enabled\n");
	    X                       break;
	    X
	    X               default:
	    X                       err = EINVAL;
	    X                       break;
	    X       }
	    X
	    X       return(err);
	    X}
	    X
	    Xint disable_rfork(struct lkm_table *lkp, int cmd, int ver) {
	    X       DISPATCH(lkp, cmd, ver, disable_rfork_load,
	    X                               disable_rfork_load, lkm_nullcmd);
	    X}
	    END-of-unrfork_mod_load.c
	    exit
	
	

Internet highlights