-----BEGIN PGP SIGNED MESSAGE----- UNSAFE TEMPORARY FILE HANDLING IN KRB4 2001-03-07 SUMMARY: A /tmp race condition exists in MIT-derived implementations of Kerberos 4. IMPACT: On a system running login daemons with Kerberos 4 support, a local user may be able to overwrite arbitrary files as root, with limited contents. This could potentially result in unauthorized root access. VULNERABLE DISTRIBUTIONS: Source distributions which may contain vulnerable code include: MIT Kerberos 5, all releases prior to krb5-1.2.2-beta1 MIT Kerberos 4 patch 10, and likely earlier releases as well Kerbnet (Cygnus implementation of Kerberos 5) Cygnus Network Security (CNS -- Cygnus implementation of Kerberos 4) Certain releases of kth-krb. A patch is available for these releases from an earlier, separate advisory. FIXES: The MIT krb5-1.2.2 release contains a fix for this bug. If you are unable to upgrade to krb5-1.2.2, the best course of action is to patch the krb4 library, and recompile or relink your login daemons. Patches below are only provided for krb5-1.2.1; additional patches against other releases may be generated and posted if requested. This announcement and code patches related to it may be found on the MIT Kerberos security advisory page at: http://web.mit.edu/kerberos/www/advisories/index.html The main MIT Kerberos web page is at: http://web.mit.edu/kerberos/www/index.html ACKNOWLEDGMENTS: Thanks to CERT, Jouko Pynnonen, and Assar Westerlund for discovering and reporting the related bug in kth-krb. DETAILS: A filesystem race condition exists in the ticket file handling code in the krb4 library. This race condition has existed since the early MIT implementations of Kerberos 4. By winning this race condition, especially while new ticket files are being created by login daemons running as root, a user may overwrite arbitrary files as root, but with limited contents. The possible contents of the overwritten files are limited to the initial contents of a normal Kerberos 4 ticket file. PATCHES AGAINST krb5-1.2.1: These patches are against the krb5-1.2.1 release. They may apply against earlier releases, though. The patches may also be found at: http://web.mit.edu/kerberos/www/advisories/krb4tkt_121_patch.txt Index: lib/krb4/dest_tkt.c =================================================================== RCS file: /cvs/krbdev/krb5/src/lib/krb4/dest_tkt.c,v retrieving revision 1.5.8.1 retrieving revision 1.5.8.2 diff -c -r1.5.8.1 -r1.5.8.2 *** dest_tkt.c 2000/04/29 01:48:10 1.5.8.1 - --- dest_tkt.c 2001/01/27 04:43:31 1.5.8.2 *************** *** 1,14 **** /* ! * dest_tkt.c * ! * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute ! * of Technology. * ! * For copying and distribution information, please see the file ! * . */ - - #include "mit-copyright.h" #include "krb.h" #include #include - --- 1,29 ---- /* ! * lib/krb4/dest_tkt.c * ! * Copyright 1985, 1986, 1987, 1988, 2000, 2001 by the Massachusetts ! * Institute of Technology. All Rights Reserved. * ! * Export of this software from the United States of America may ! * require a specific license from the United States Government. ! * It is the responsibility of any person or organization contemplating ! * export to obtain such a license before exporting. ! * ! * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and ! * distribute this software and its documentation for any purpose and ! * without fee is hereby granted, provided that the above copyright ! * notice appear in all copies and that both that copyright notice and ! * this permission notice appear in supporting documentation, and that ! * the name of M.I.T. not be used in advertising or publicity pertaining ! * to distribution of the software without specific, written prior ! * permission. Furthermore if you modify this software you must label ! * your software as modified software and not distribute it in such a ! * fashion that it might be confused with the original M.I.T. software. ! * M.I.T. makes no representations about the suitability of ! * this software for any purpose. It is provided "as is" without express ! * or implied warranty. */ #include "krb.h" #include #include *************** *** 17,28 **** - --- 32,60 ---- #ifdef TKT_SHMEM #include #endif + #ifdef HAVE_UNISTD_H + #include + #endif #include #ifndef O_SYNC #define O_SYNC 0 #endif + #ifdef HAVE_SETEUID + #define do_seteuid(e) seteuid((e)) + #else + #ifdef HAVE_SETRESUID + #define do_seteuid(e) setresuid(-1, (e), -1) + #else + #ifdef HAVE_SETREUID + #define do_seteuid(e) setreuid(geteuid(), (e)) + #else + #define do_seteuid(e) (errno = EPERM, -1) + #endif + #endif + #endif + /* * dest_tkt() is used to destroy the ticket store upon logout. * If the ticket file does not exist, dest_tkt() returns RET_TKFIL. *************** *** 38,47 **** char *file = TKT_FILE; int i,fd; extern int errno; ! struct stat statb; char buf[BUFSIZ]; #ifdef TKT_SHMEM char shmidname[MAXPATHLEN]; #endif /* TKT_SHMEM */ /* If ticket cache selector is null, use default cache. */ - --- 70,82 ---- char *file = TKT_FILE; int i,fd; extern int errno; ! int ret; ! struct stat statpre, statpost; char buf[BUFSIZ]; + uid_t me, metoo; #ifdef TKT_SHMEM char shmidname[MAXPATHLEN]; + size_t shmidlen; #endif /* TKT_SHMEM */ /* If ticket cache selector is null, use default cache. */ *************** *** 49,70 **** file = tkt_string(); errno = 0; ! if (lstat(file,&statb) < 0) goto out; ! ! if (!(statb.st_mode & S_IFREG) ! #ifdef notdef ! || statb.st_mode & 077 ! #endif ! ) goto out; ! ! if ((fd = open(file, O_RDWR|O_SYNC, 0)) < 0) goto out; memset(buf, 0, BUFSIZ); ! ! for (i = 0; i < statb.st_size; i += BUFSIZ) if (write(fd, buf, BUFSIZ) != BUFSIZ) { #ifndef NO_FSYNC (void) fsync(fd); - --- 84,139 ---- file = tkt_string(); errno = 0; ! ret = KSUCCESS; ! me = getuid(); ! metoo = geteuid(); ! ! if (lstat(file, &statpre) < 0) ! return (errno == ENOENT) ? RET_TKFIL : KFAILURE; ! /* ! * This does not guard against certain cases that are vulnerable ! * to race conditions, such as world-writable or group-writable ! * directories that are not stickybitted, or untrusted path ! * components. In all other cases, the following checks should be ! * sufficient. It is assumed that the aforementioned certain ! * vulnerable cases are unlikely to arise on a well-administered ! * system where the user is not deliberately being stupid. ! */ ! if (!(statpre.st_mode & S_IFREG) || me != statpre.st_uid ! || statpre.st_nlink != 1) ! return KFAILURE; ! /* ! * Yes, we do uid twiddling here. It's not optimal, but some ! * applications may expect that the ruid is what should really own ! * the ticket file, e.g. setuid applications. ! */ ! if (me != metoo && do_seteuid(me) < 0) ! return KFAILURE; ! if ((fd = open(file, O_RDWR|O_SYNC, 0)) < 0) { ! ret = (errno == ENOENT) ? RET_TKFIL : KFAILURE; goto out; ! } ! /* ! * Do some additional paranoid things. The worst-case situation ! * is that a user may be fooled into opening a non-regular file ! * briefly if the file is in a directory with improper ! * permissions. ! */ ! if (fstat(fd, &statpost) < 0) { ! (void)close(fd); ! ret = KFAILURE; goto out; ! } ! if (statpre.st_dev != statpost.st_dev ! || statpre.st_ino != statpost.st_ino) { ! (void)close(fd); ! errno = 0; ! ret = KFAILURE; goto out; + } memset(buf, 0, BUFSIZ); ! for (i = 0; i < statpost.st_size; i += BUFSIZ) if (write(fd, buf, BUFSIZ) != BUFSIZ) { #ifndef NO_FSYNC (void) fsync(fd); *************** *** 81,97 **** (void) unlink(file); out: ! if (errno == ENOENT) return RET_TKFIL; ! else if (errno != 0) return KFAILURE; #ifdef TKT_SHMEM /* * handle the shared memory case */ ! (void) strncpy(shmidname, file, sizeof(shmidname) - 1); ! shmidname[sizeof(shmidname) - 1] = '\0'; ! (void) strcat(shmidname, ".shm", sizeof(shmidname) - 1 - strlen(shmidname)); ! if ((i = krb_shm_dest(shmidname)) != KSUCCESS) ! return(i); ! #endif /* TKT_SHMEM */ ! return(KSUCCESS); } - --- 150,171 ---- (void) unlink(file); out: ! if (me != metoo && do_seteuid(metoo) < 0) ! return KFAILURE; ! if (ret != KSUCCESS) ! return ret; ! #ifdef TKT_SHMEM /* * handle the shared memory case */ ! shmidlen = strlen(file) + sizeof(".shm"); ! if (shmidlen > sizeof(shmidname)) ! return RET_TKFIL; ! (void)strcpy(shmidname, file); ! (void)strcat(shmidname, ".shm"); ! return krb_shm_dest(shmidname); ! #else /* !TKT_SHMEM */ ! return KSUCCESS; ! #endif /* !TKT_SHMEM */ } Index: lib/krb4/in_tkt.c =================================================================== RCS file: /cvs/krbdev/krb5/src/lib/krb4/in_tkt.c,v retrieving revision 1.6.8.1 retrieving revision 1.6.8.2 diff -c -r1.6.8.1 -r1.6.8.2 *** in_tkt.c 2000/04/29 01:48:10 1.6.8.1 - --- in_tkt.c 2001/01/27 04:43:32 1.6.8.2 *************** *** 1,14 **** /* ! * in_tkt.c * ! * Copyright 1985, 1986, 1987, 1988 by the Massachusetts Institute ! * of Technology. * ! * For copying and distribution information, please see the file ! * . */ - - #include "mit-copyright.h" #include #include #include "krb.h" - --- 1,29 ---- /* ! * lib/krb4/in_tkt.c * ! * Copyright 1985, 1986, 1987, 1988, 2000, 2001 by the Massachusetts ! * Institute of Technology. All Rights Reserved. * ! * Export of this software from the United States of America may ! * require a specific license from the United States Government. ! * It is the responsibility of any person or organization contemplating ! * export to obtain such a license before exporting. ! * ! * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and ! * distribute this software and its documentation for any purpose and ! * without fee is hereby granted, provided that the above copyright ! * notice appear in all copies and that both that copyright notice and ! * this permission notice appear in supporting documentation, and that ! * the name of M.I.T. not be used in advertising or publicity pertaining ! * to distribution of the software without specific, written prior ! * permission. Furthermore if you modify this software you must label ! * your software as modified software and not distribute it in such a ! * fashion that it might be confused with the original M.I.T. software. ! * M.I.T. makes no representations about the suitability of ! * this software for any purpose. It is provided "as is" without express ! * or implied warranty. */ #include #include #include "krb.h" *************** *** 34,40 **** #define do_seteuid(e) seteuid((e)) #else #ifdef HAVE_SETRESUID ! #define do_seteuid(e) setresuid(getuid(), (e), geteuid()) #else #ifdef HAVE_SETREUID #define do_seteuid(e) setreuid(geteuid(), (e)) - --- 49,55 ---- #define do_seteuid(e) seteuid((e)) #else #ifdef HAVE_SETRESUID ! #define do_seteuid(e) setresuid(-1, (e), -1) #else #ifdef HAVE_SETREUID #define do_seteuid(e) setreuid(geteuid(), (e)) *************** *** 55,61 **** { int tktfile; uid_t me, metoo, getuid(), geteuid(); ! struct stat buf; int count; char *file = TKT_FILE; int fd; - --- 70,76 ---- { int tktfile; uid_t me, metoo, getuid(), geteuid(); ! struct stat statpre, statpost; int count; char *file = TKT_FILE; int fd; *************** *** 72,91 **** me = getuid (); metoo = geteuid(); ! if (lstat(file,&buf) == 0) { ! if (buf.st_uid != me || !(buf.st_mode & S_IFREG) || ! buf.st_mode & 077) { if (krb_debug) fprintf(stderr,"Error initializing %s",file); return(KFAILURE); } /* file already exists, and permissions appear ok, so nuke it */ ! if ((fd = open(file, O_RDWR|O_SYNC, 0)) < 0) goto out; /* can't zero it, but we can still try truncating it */ memset(charbuf, 0, sizeof(charbuf)); ! for (i = 0; i < buf.st_size; i += sizeof(charbuf)) if (write(fd, charbuf, sizeof(charbuf)) != sizeof(charbuf)) { #ifndef NO_FSYNC (void) fsync(fd); - --- 87,135 ---- me = getuid (); metoo = geteuid(); ! if (lstat(file, &statpre) == 0) { ! if (statpre.st_uid != me || !(statpre.st_mode & S_IFREG) ! || statpre.st_nlink != 1 || statpre.st_mode & 077) { if (krb_debug) fprintf(stderr,"Error initializing %s",file); return(KFAILURE); } + /* + * Yes, we do uid twiddling here. It's not optimal, but some + * applications may expect that the ruid is what should really + * own the ticket file, e.g. setuid applications. + */ + if (me != metoo && do_seteuid(me) < 0) + return KFAILURE; /* file already exists, and permissions appear ok, so nuke it */ ! fd = open(file, O_RDWR|O_SYNC, 0); ! (void)unlink(file); ! if (me != metoo && do_seteuid(metoo) < 0) ! return KFAILURE; ! if (fd < 0) { goto out; /* can't zero it, but we can still try truncating it */ + } + + /* + * Do some additional paranoid things. The worst-case + * situation is that a user may be fooled into opening a + * non-regular file briefly if the file is in a directory with + * improper permissions. + */ + if (fstat(fd, &statpost) < 0) { + (void)close(fd); + goto out; + } + if (statpre.st_dev != statpost.st_dev + || statpre.st_ino != statpost.st_ino) { + (void)close(fd); + errno = 0; + goto out; + } memset(charbuf, 0, sizeof(charbuf)); ! for (i = 0; i < statpost.st_size; i += sizeof(charbuf)) if (write(fd, charbuf, sizeof(charbuf)) != sizeof(charbuf)) { #ifndef NO_FSYNC (void) fsync(fd); *************** *** 117,128 **** /* Set umask to ensure that we have write access on the created ticket file. */ mask = umask(077); ! if ((tktfile = creat(file,0600)) < 0) { ! umask(mask); ! if (krb_debug) ! fprintf(stderr,"Error initializing %s",TKT_FILE); ! return(KFAILURE); ! } umask(mask); if (me != metoo) { if (do_seteuid(metoo) < 0) { - --- 161,167 ---- /* Set umask to ensure that we have write access on the created ticket file. */ mask = umask(077); ! tktfile = open(file, O_RDWR|O_SYNC|O_CREAT|O_EXCL, 0600); umask(mask); if (me != metoo) { if (do_seteuid(metoo) < 0) { *************** *** 134,152 **** if (krb_debug) printf("swapped UID's %d and %d\n",me,metoo); } ! if (lstat(file,&buf) < 0) { if (krb_debug) fprintf(stderr,"Error initializing %s",TKT_FILE); return(KFAILURE); } - - - - if (buf.st_uid != me || !(buf.st_mode & S_IFREG) || - - buf.st_mode & 077) { - - if (krb_debug) - - fprintf(stderr,"Error initializing %s",TKT_FILE); - - return(KFAILURE); - - } - - count = strlen(pname)+1; if (write(tktfile,pname,count) != count) { (void) close(tktfile); - --- 173,183 ---- if (krb_debug) printf("swapped UID's %d and %d\n",me,metoo); } ! if (tktfile < 0) { if (krb_debug) fprintf(stderr,"Error initializing %s",TKT_FILE); return(KFAILURE); } count = strlen(pname)+1; if (write(tktfile,pname,count) != count) { (void) close(tktfile); Index: lib/krb4/tf_util.c =================================================================== RCS file: /cvs/krbdev/krb5/src/lib/krb4/tf_util.c,v retrieving revision 1.12.4.1 retrieving revision 1.12.4.2 diff -c -r1.12.4.1 -r1.12.4.2 *** tf_util.c 2000/04/29 01:48:11 1.12.4.1 - --- tf_util.c 2001/01/27 04:43:32 1.12.4.2 *************** *** 1,20 **** /* ! * tf_util.c * ! * Copyright 1987, 1988 by the Massachusetts Institute of Technology. * ! * For copying and distribution information, please see the file ! * . */ - - #include "mit-copyright.h" - - #include "krb.h" #include "k5-int.h" #include #include #include #include #include - --- 1,38 ---- /* ! * lib/krb4/tf_util.c * ! * Copyright 1985, 1986, 1987, 1988, 2000, 2001 by the Massachusetts ! * Institute of Technology. All Rights Reserved. * ! * Export of this software from the United States of America may ! * require a specific license from the United States Government. ! * It is the responsibility of any person or organization contemplating ! * export to obtain such a license before exporting. ! * ! * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and ! * distribute this software and its documentation for any purpose and ! * without fee is hereby granted, provided that the above copyright ! * notice appear in all copies and that both that copyright notice and ! * this permission notice appear in supporting documentation, and that ! * the name of M.I.T. not be used in advertising or publicity pertaining ! * to distribution of the software without specific, written prior ! * permission. Furthermore if you modify this software you must label ! * your software as modified software and not distribute it in such a ! * fashion that it might be confused with the original M.I.T. software. ! * M.I.T. makes no representations about the suitability of ! * this software for any purpose. It is provided "as is" without express ! * or implied warranty. */ #include "krb.h" #include "k5-int.h" #include #include #include + #ifdef HAVE_UNISTD_H + #include + #endif #include #include *************** *** 44,50 **** #ifdef NEED_UTIMES #include - - #include #ifdef __SCO__ #include #endif - --- 62,67 ---- *************** *** 62,67 **** - --- 79,98 ---- } #endif + #ifdef HAVE_SETEUID + #define do_seteuid(e) seteuid((e)) + #else + #ifdef HAVE_SETRESUID + #define do_seteuid(e) setresuid(-1, (e), -1) + #else + #ifdef HAVE_SETREUID + #define do_seteuid(e) setreuid(geteuid(), (e)) + #else + #define do_seteuid(e) (errno = EPERM, -1) + #endif + #endif + #endif + /* * fd must be initialized to something that won't ever occur as a real * file descriptor. Since open(2) returns only non-negative numbers as *************** *** 149,155 **** int rw; { int wflag; ! uid_t me= getuid(); struct stat stat_buf, stat_buffd; #ifdef TKT_SHMEM char shmidname[MAXPATHLEN]; - --- 180,186 ---- int rw; { int wflag; ! uid_t me, metoo; struct stat stat_buf, stat_buffd; #ifdef TKT_SHMEM char shmidname[MAXPATHLEN]; *************** *** 163,168 **** - --- 194,200 ---- } me = getuid(); + metoo = geteuid(); switch (rw) { case R_TKT_FIL: *************** *** 196,203 **** - --- 228,257 ---- curpos = sizeof(tfbfr); #ifdef TKT_SHMEM + if (lstat(shmidname, &stat_buf) < 0) { + switch (errno) { + case ENOENT: + return NO_TKT_FIL; + default: + return TKT_FIL_ACC; + } + } + if (stat_buf.st_uid != me || !(stat_buf.st_mode & S_IFREG) + || stat_buf.st_nlink != 1 || stat_buf.st_mode & 077) { + return TKT_FIL_ACC; + } + + /* + * Yes, we do uid twiddling here. It's not optimal, but some + * applications may expect that the ruid is what should really own + * the ticket file, e.g. setuid applications. + */ + if (me != metoo && do_seteuid(me) < 0) + return KFAILURE; sfp = fopen(shmidname, "r"); /* only need read/write on the actual tickets */ + if (me != metoo && do_seteuid(metoo) < 0) + return KFAILURE; if (sfp == 0) { switch(errno) { case ENOENT: *************** *** 207,216 **** } } ! /* lstat() and fstat() the file to check that the file we opened is the * ! * one we think it is, and to check ownership. */ ! if ((fstat(sfp->_file, &stat_buffd) < 0) || ! (lstat(shmidname, &stat_buf) < 0)) { (void) close(fd); fd = -1; switch(errno) { - --- 261,271 ---- } } ! /* ! * fstat() the file to check that the file we opened is the one we ! * think it is. ! */ ! if (fstat(fileno(sfp), &stat_buffd) < 0) { (void) close(fd); fd = -1; switch(errno) { *************** *** 271,278 **** - --- 326,350 ---- tmp_shm_addr = krb_shm_addr; #endif /* TKT_SHMEM */ + if (lstat(tf_name, &stat_buf) < 0) { + switch (errno) { + case ENOENT: + return NO_TKT_FIL; + default: + return TKT_FIL_ACC; + } + } + if (stat_buf.st_uid != me || !(stat_buf.st_mode & S_IFREG) + || stat_buf.st_nlink != 1 || stat_buf.st_mode & 077) { + return TKT_FIL_ACC; + } + if (wflag) { + if (me != metoo && do_seteuid(me) < 0) + return KFAILURE; fd = open(tf_name, O_RDWR, 0600); + if (me != metoo && do_seteuid(metoo) < 0) + return KFAILURE; if (fd < 0) { switch(errno) { case ENOENT: *************** *** 281,290 **** return TKT_FIL_ACC; } } ! /* lstat() and fstat() the file to check that the file we opened is the * ! * one we think it is, and to check ownership. */ ! if ((fstat(fd, &stat_buffd) < 0) || ! (lstat(tf_name, &stat_buf) < 0)) { (void) close(fd); fd = -1; switch(errno) { - --- 353,363 ---- return TKT_FIL_ACC; } } ! /* ! * fstat() the file to check that the file we opened is the ! * one we think it is, and to check ownership. ! */ ! if (fstat(fd, &stat_buffd) < 0) { (void) close(fd); fd = -1; switch(errno) { *************** *** 327,333 **** - --- 400,410 ---- * for read-only operations and locked for shared access. */ + if (me != metoo && do_seteuid(me) < 0) + return KFAILURE; fd = open(tf_name, O_RDONLY, 0600); + if (me != metoo && do_seteuid(metoo) < 0) + return KFAILURE; if (fd < 0) { switch(errno) { case ENOENT: *************** *** 336,345 **** return TKT_FIL_ACC; } } ! /* lstat() and fstat() the file to check that the file we opened is the * ! * one we think it is, and to check ownership. */ ! if ((fstat(fd, &stat_buffd) < 0) || ! (lstat(tf_name, &stat_buf) < 0)) { (void) close(fd); fd = -1; switch(errno) { - --- 413,423 ---- return TKT_FIL_ACC; } } ! /* ! * fstat() the file to check that the file we opened is the one we ! * think it is, and to check ownership. ! */ ! if (fstat(fd, &stat_buffd) < 0) { (void) close(fd); fd = -1; switch(errno) { -----BEGIN PGP SIGNATURE----- Version: PGP 6.5.8 iQCVAwUBOqcBpqbDgE/zdoE9AQEfRAP/XEeuEBgVtWMjsgdq+W+hqkl0ys9ftTHW ITY4rIgD9eQLzchyKeyBS8gEeCE7qicwO1rpNKlSLzzIf9H3CxtOlAC2T78yf5EH vwYDVT+qxoBZU90xKKj/66EnvewkTQB7RF2iMa9CahOBtEfFHxXLB9fRfEuIyVjg XYrh4a7vWdE= =2BwU -----END PGP SIGNATURE-----