X-Git-Url: https://vcs.fsf.org/?a=blobdiff_plain;f=src%2Fsrc%2Ftransports%2Fappendfile.c;h=e537615824300233b47cb9d5425cf461c7a38d6b;hb=3346ab0144239462a8690e011bff1df8fd504076;hp=df2ce1cd8772f2037456270031a81fe7298eb206;hpb=0a49a7a4f1090b6f1ce1d0f9d969804c9226b53e;p=exim.git diff --git a/src/src/transports/appendfile.c b/src/src/transports/appendfile.c index df2ce1cd8..e53761582 100644 --- a/src/src/transports/appendfile.c +++ b/src/src/transports/appendfile.c @@ -1,4 +1,4 @@ -/* $Cambridge: exim/src/src/transports/appendfile.c,v 1.24 2009/11/16 19:50:39 nm4 Exp $ */ +/* $Cambridge: exim/src/src/transports/appendfile.c,v 1.27 2010/06/07 00:12:42 pdp Exp $ */ /************************************************* * Exim - an Internet mail transport agent * @@ -1806,6 +1806,18 @@ if (!isdirectory) goto RETURN; } + /* Just in case this is a sticky-bit mail directory, we don't want + users to be able to create hard links to other users' files. */ + + if (statbuf.st_nlink != 1) + { + addr->basic_errno = ERRNO_NOTREGULAR; + addr->message = string_sprintf("mailbox %s%s has too many links (%d)", + filename, islink? " (symlink)" : "", statbuf.st_nlink); + goto RETURN; + + } + /* If symlinks are permitted (not recommended), the lstat() above will have found the symlink. Its ownership has just been checked; go round the loop again, using stat() instead of lstat(). That will never yield a @@ -1998,6 +2010,8 @@ if (!isdirectory) #ifdef SUPPORT_MBX else if (ob->use_mbx_lock) { + int mbx_tmp_oflags; + struct stat lstatbuf, statbuf2; if (apply_lock(fd, F_RDLCK, ob->use_fcntl, ob->lock_fcntl_timeout, ob->use_flock, ob->lock_flock_timeout) >= 0 && fstat(fd, &statbuf) >= 0) @@ -2005,6 +2019,21 @@ if (!isdirectory) sprintf(CS mbx_lockname, "/tmp/.%lx.%lx", (long)statbuf.st_dev, (long)statbuf.st_ino); + /* + * 2010-05-29: SECURITY + * Dan Rosenberg reported the presence of a race-condition in the + * original code here. Beware that many systems still allow symlinks + * to be followed in /tmp so an attacker can create a symlink pointing + * elsewhere between a stat and an open, which we should avoid + * following. + * + * It's unfortunate that we can't just use all the heavily debugged + * locking from above. + * + * Also: remember to mirror changes into exim_lock.c */ + + /* first leave the old pre-check in place, it provides better + * diagnostics for common cases */ if (Ulstat(mbx_lockname, &statbuf) >= 0) { if ((statbuf.st_mode & S_IFMT) == S_IFLNK) @@ -2023,7 +2052,19 @@ if (!isdirectory) } } - mbx_lockfd = Uopen(mbx_lockname, O_RDWR | O_CREAT, ob->lockfile_mode); + /* If we could just declare "we must be the ones who create this + * file" then a hitching post in a subdir would work, since a + * subdir directly in /tmp/ which we create wouldn't follow links + * but this isn't our locking logic, so we can't safely change the + * file existence rules. */ + + /* On systems which support O_NOFOLLOW, it's the easiest and most + * obviously correct security fix */ + mbx_tmp_oflags = O_RDWR | O_CREAT; +#ifdef O_NOFOLLOW + mbx_tmp_oflags |= O_NOFOLLOW; +#endif + mbx_lockfd = Uopen(mbx_lockname, mbx_tmp_oflags, ob->lockfile_mode); if (mbx_lockfd < 0) { addr->basic_errno = ERRNO_LOCKFAILED; @@ -2032,6 +2073,60 @@ if (!isdirectory) goto RETURN; } + if (Ulstat(mbx_lockname, &lstatbuf) < 0) + { + addr->basic_errno = ERRNO_LOCKFAILED; + addr->message = string_sprintf("attempting to lstat open MBX " + "lock file %s: %s", mbx_lockname, strerror(errno)); + goto RETURN; + } + if (fstat(mbx_lockfd, &statbuf2) < 0) + { + addr->basic_errno = ERRNO_LOCKFAILED; + addr->message = string_sprintf("attempting to stat fd of open MBX " + "lock file %s: %s", mbx_lockname, strerror(errno)); + goto RETURN; + } + + /* + * At this point: + * statbuf: if exists, is file which existed prior to opening the + * lockfile, might have been replaced since then + * statbuf2: result of stat'ing the open fd, is what was actually + * opened + * lstatbuf: result of lstat'ing the filename immediately after + * the open but there's a race condition again between + * those two steps: before open, symlink to foo, after + * open but before lstat have one of: + * * was no symlink, so is the opened file + * (we created it, no messing possible after that point) + * * hardlink to foo + * * symlink elsewhere + * * hardlink elsewhere + * * new file/other + * Don't want to compare to device of /tmp because some modern systems + * have regressed to having /tmp be the safe actual filesystem as + * valuable data, so is mostly worthless, unless we assume that *only* + * Linux systems do this and that all Linux has O_NOFOLLOW. Something + * for further consideration. + * No point in doing a readlink on the lockfile as that will always be + * at a different point in time from when we open it, so tells us + * nothing; attempts to clean up and delete after ourselves would risk + * deleting a *third* filename. + */ + if ((statbuf2.st_nlink > 1) || + (lstatbuf.st_nlink > 1) || + (!S_ISREG(lstatbuf.st_mode)) || + (lstatbuf.st_dev != statbuf2.st_dev) || + (lstatbuf.st_ino != statbuf2.st_ino)) + { + addr->basic_errno = ERRNO_LOCKFAILED; + addr->message = string_sprintf("RACE CONDITION detected: " + "mismatch post-initial-checks between \"%s\" and opened " + "fd lead us to abort!", mbx_lockname); + goto RETURN; + } + (void)Uchmod(mbx_lockname, ob->lockfile_mode); if (apply_lock(mbx_lockfd, F_WRLCK, ob->use_fcntl,