fix some race conditions for file opens
authorEric Allman <eric@ucbvax.Berkeley.EDU>
Tue, 7 Mar 1995 07:11:57 +0000 (23:11 -0800)
committerEric Allman <eric@ucbvax.Berkeley.EDU>
Tue, 7 Mar 1995 07:11:57 +0000 (23:11 -0800)
SCCS-vsn: usr.sbin/sendmail/src/recipient.c 8.70
SCCS-vsn: usr.sbin/sendmail/src/conf.h 8.138
SCCS-vsn: usr.sbin/sendmail/src/domain.c 8.32
SCCS-vsn: usr.sbin/sendmail/src/sendmail.h 8.95
SCCS-vsn: usr.sbin/sendmail/src/savemail.c 8.53
SCCS-vsn: usr.sbin/sendmail/src/util.c 8.55

usr/src/usr.sbin/sendmail/src/conf.h
usr/src/usr.sbin/sendmail/src/domain.c
usr/src/usr.sbin/sendmail/src/recipient.c
usr/src/usr.sbin/sendmail/src/savemail.c
usr/src/usr.sbin/sendmail/src/sendmail.h
usr/src/usr.sbin/sendmail/src/util.c

index 4762ac1..481b4c8 100644 (file)
@@ -5,7 +5,7 @@
  *
  * %sccs.include.redist.c%
  *
  *
  * %sccs.include.redist.c%
  *
- *     @(#)conf.h      8.137 (Berkeley) %G%
+ *     @(#)conf.h      8.138 (Berkeley) %G%
  */
 
 /*
  */
 
 /*
@@ -1259,11 +1259,14 @@ extern struct group     *getgrent(), *getgrnam(), *getgrgid();
 #if !defined(S_ISLNK) && defined(S_IFLNK)
 # define S_ISLNK(foo)  ((foo & S_IFMT) == S_IFLNK)
 #endif
 #if !defined(S_ISLNK) && defined(S_IFLNK)
 # define S_ISLNK(foo)  ((foo & S_IFMT) == S_IFLNK)
 #endif
+#ifndef S_IWUSR
+# define S_IWUSR               0200
+#endif
 #ifndef S_IWGRP
 #ifndef S_IWGRP
-#define S_IWGRP                020
+# define S_IWGRP               0020
 #endif
 #ifndef S_IWOTH
 #endif
 #ifndef S_IWOTH
-#define S_IWOTH                002
+# define S_IWOTH               0002
 #endif
 
 /*
 #endif
 
 /*
index acdbfb1..4499072 100644 (file)
@@ -10,9 +10,9 @@
 
 #ifndef lint
 #if NAMED_BIND
 
 #ifndef lint
 #if NAMED_BIND
-static char sccsid[] = "@(#)domain.c   8.31 (Berkeley) %G% (with name server)";
+static char sccsid[] = "@(#)domain.c   8.32 (Berkeley) %G% (with name server)";
 #else
 #else
-static char sccsid[] = "@(#)domain.c   8.31 (Berkeley) %G% (without name server)";
+static char sccsid[] = "@(#)domain.c   8.32 (Berkeley) %G% (without name server)";
 #endif
 #endif /* not lint */
 
 #endif
 #endif /* not lint */
 
@@ -695,7 +695,8 @@ gethostalias(host)
        static char hbuf[MAXDNAME];
 
        fname = getenv("HOSTALIASES");
        static char hbuf[MAXDNAME];
 
        fname = getenv("HOSTALIASES");
-       if (fname == NULL || (fp = fopen(fname, "r")) == NULL)
+       if (fname == NULL ||
+           (fp = safefopen(fname, O_RDONLY, 0, SFF_ANYFILE)) == NULL)
                return NULL;
        while (fgets(buf, sizeof buf, fp) != NULL)
        {
                return NULL;
        while (fgets(buf, sizeof buf, fp) != NULL)
        {
index 5222504..b28583d 100644 (file)
@@ -7,7 +7,7 @@
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-static char sccsid[] = "@(#)recipient.c        8.69 (Berkeley) %G%";
+static char sccsid[] = "@(#)recipient.c        8.70 (Berkeley) %G%";
 #endif /* not lint */
 
 # include "sendmail.h"
 #endif /* not lint */
 
 # include "sendmail.h"
@@ -406,7 +406,7 @@ recipient(a, sendq, aliaslevel, e)
                        usrerr("550 Address %s is unsafe for mailing to files",
                                a->q_alias->q_paddr);
                }
                        usrerr("550 Address %s is unsafe for mailing to files",
                                a->q_alias->q_paddr);
                }
-               else if (!writable(buf, getctladdr(a), SFF_ANYFILE))
+               else if (!writable(buf, getctladdr(a), SFF_CREAT))
                {
                        a->q_flags |= QBADADDR;
                        giveresponse(EX_CANTCREAT, m, NULL, a->q_alias,
                {
                        a->q_flags |= QBADADDR;
                        giveresponse(EX_CANTCREAT, m, NULL, a->q_alias,
@@ -685,33 +685,10 @@ writable(filename, ctladdr, flags)
        int bits;
        register char *p;
        char *uname;
        int bits;
        register char *p;
        char *uname;
-       struct stat stb;
-       extern char RealUserName[];
 
        if (tTd(29, 5))
                printf("writable(%s, %x)\n", filename, flags);
 
 
        if (tTd(29, 5))
                printf("writable(%s, %x)\n", filename, flags);
 
-#ifdef HASLSTAT
-       if ((bitset(SFF_NOSLINK, flags) ? lstat(filename, &stb)
-                                       : stat(filename, &stb)) < 0)
-#else
-       if (stat(filename, &stb) < 0)
-#endif
-       {
-               /* file does not exist -- see if directory is safe */
-               p = strrchr(filename, '/');
-               if (p == NULL)
-               {
-                       errno = ENOTDIR;
-                       return FALSE;
-               }
-               *p = '\0';
-               errno = safefile(filename, RealUid, RealGid, RealUserName,
-                                SFF_MUSTOWN, S_IWRITE|S_IEXEC);
-               *p = '/';
-               return errno == 0;
-       }
-
 #ifdef SUID_ROOT_FILES_OK
        /* really ought to be passed down -- and not a good idea */
        flags |= SFF_ROOTOK;
 #ifdef SUID_ROOT_FILES_OK
        /* really ought to be passed down -- and not a good idea */
        flags |= SFF_ROOTOK;
@@ -721,14 +698,6 @@ writable(filename, ctladdr, flags)
        **  File does exist -- check that it is writable.
        */
 
        **  File does exist -- check that it is writable.
        */
 
-       if (bitset(0111, stb.st_mode))
-       {
-               if (tTd(29, 5))
-                       printf("failed (mode %o: x bits)\n", stb.st_mode);
-               errno = EPERM;
-               return (FALSE);
-       }
-
        if (ctladdr != NULL && geteuid() == 0)
        {
                euid = ctladdr->q_uid;
        if (ctladdr != NULL && geteuid() == 0)
        {
                euid = ctladdr->q_uid;
@@ -738,6 +707,8 @@ writable(filename, ctladdr, flags)
 #ifdef RUN_AS_REAL_UID
        else
        {
 #ifdef RUN_AS_REAL_UID
        else
        {
+               extern char RealUserName[];
+
                euid = RealUid;
                egid = RealGid;
                uname = RealUserName;
                euid = RealUid;
                egid = RealGid;
                uname = RealUserName;
@@ -761,23 +732,9 @@ writable(filename, ctladdr, flags)
        if (egid == 0)
                egid = DefGid;
        if (geteuid() == 0)
        if (egid == 0)
                egid = DefGid;
        if (geteuid() == 0)
-       {
-               if (bitset(S_ISUID, stb.st_mode) &&
-                   (stb.st_uid != 0 || bitset(SFF_ROOTOK, flags)))
-               {
-                       euid = stb.st_uid;
-                       uname = NULL;
-               }
-               if (bitset(S_ISGID, stb.st_mode) &&
-                   (stb.st_gid != 0 || bitset(SFF_ROOTOK, flags)))
-                       egid = stb.st_gid;
-       }
+               flags |= SFF_SETUIDOK;
 
 
-       if (tTd(29, 5))
-               printf("\teu/gid=%d/%d, st_u/gid=%d/%d\n",
-                       euid, egid, stb.st_uid, stb.st_gid);
-
-       errno = safefile(filename, euid, egid, uname, flags, S_IWRITE);
+       errno = safefile(filename, euid, egid, uname, flags, S_IWRITE, NULL);
        return errno == 0;
 }
 \f/*
        return errno == 0;
 }
 \f/*
@@ -819,10 +776,6 @@ writable(filename, ctladdr, flags)
 static jmp_buf CtxIncludeTimeout;
 static void    includetimeout();
 
 static jmp_buf CtxIncludeTimeout;
 static void    includetimeout();
 
-#ifndef S_IWOTH
-# define S_IWOTH       (S_IWRITE >> 6)
-#endif
-
 int
 include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
        char *fname;
 int
 include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
        char *fname;
@@ -931,7 +884,7 @@ include(fname, forwarding, ctladdr, sendq, aliaslevel, e)
                ev = NULL;
 
        /* the input file must be marked safe */
                ev = NULL;
 
        /* the input file must be marked safe */
-       rval = safefile(fname, uid, gid, uname, sfflags, S_IREAD);
+       rval = safefile(fname, uid, gid, uname, sfflags, S_IREAD, NULL);
        if (rval != 0)
        {
                /* don't use this :include: file */
        if (rval != 0)
        {
                /* don't use this :include: file */
index 93ebbbb..09eb2b7 100644 (file)
@@ -7,7 +7,7 @@
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-static char sccsid[] = "@(#)savemail.c 8.52 (Berkeley) %G%";
+static char sccsid[] = "@(#)savemail.c 8.53 (Berkeley) %G%";
 #endif /* not lint */
 
 # include "sendmail.h"
 #endif /* not lint */
 
 # include "sendmail.h"
@@ -364,12 +364,13 @@ savemail(e, sendbody)
 
                        strcpy(buf, _PATH_VARTMP);
                        strcat(buf, "dead.letter");
 
                        strcpy(buf, _PATH_VARTMP);
                        strcat(buf, "dead.letter");
-                       if (!writable(buf, NULLADDR, SFF_NOSLINK))
+                       if (!writable(buf, NULLADDR, SFF_NOSLINK|SFF_CREAT))
                        {
                                state = ESM_PANIC;
                                break;
                        }
                        {
                                state = ESM_PANIC;
                                break;
                        }
-                       fp = dfopen(buf, O_WRONLY|O_CREAT|O_APPEND, FileMode);
+                       fp = safefopen(buf, O_WRONLY|O_CREAT|O_APPEND,
+                                       FileMode, SFF_NOSLINK);
                        if (fp == NULL)
                        {
                                state = ESM_PANIC;
                        if (fp == NULL)
                        {
                                state = ESM_PANIC;
index 78328a2..b4551ab 100644 (file)
@@ -5,7 +5,7 @@
  *
  * %sccs.include.redist.c%
  *
  *
  * %sccs.include.redist.c%
  *
- *     @(#)sendmail.h  8.94 (Berkeley) %G%
+ *     @(#)sendmail.h  8.95 (Berkeley) %G%
  */
 
 /*
  */
 
 /*
@@ -15,7 +15,7 @@
 # ifdef _DEFINE
 # define EXTERN
 # ifndef lint
 # ifdef _DEFINE
 # define EXTERN
 # ifndef lint
-static char SmailSccsId[] =    "@(#)sendmail.h 8.94            %G%";
+static char SmailSccsId[] =    "@(#)sendmail.h 8.95            %G%";
 # endif
 # else /*  _DEFINE */
 # define EXTERN extern
 # endif
 # else /*  _DEFINE */
 # define EXTERN extern
@@ -812,6 +812,8 @@ struct prival
 #define SFF_NOSLINK            0x0002  /* file cannot be a symbolic link */
 #define SFF_ROOTOK             0x0004  /* ok for root to own this file */
 #define SFF_NOPATHCHECK                0x0010  /* don't bother checking dir path */
 #define SFF_NOSLINK            0x0002  /* file cannot be a symbolic link */
 #define SFF_ROOTOK             0x0004  /* ok for root to own this file */
 #define SFF_NOPATHCHECK                0x0010  /* don't bother checking dir path */
+#define SFF_SETUIDOK           0x0020  /* setuid files are ok */
+#define SFF_CREAT              0x0040  /* ok to create file if necessary */
 
 
 /*
 
 
 /*
@@ -1076,6 +1078,7 @@ extern void               readaliases __P((MAP *, FILE *, bool, bool));
 extern void            finis __P(());
 extern void            clrevent __P((EVENT *));
 extern void            setsender __P((char *, ENVELOPE *, char **, bool));
 extern void            finis __P(());
 extern void            clrevent __P((EVENT *));
 extern void            setsender __P((char *, ENVELOPE *, char **, bool));
+extern FILE            *safefopen __P((char *, int, int, int));
 
 /* ellipsis is a different case though */
 #ifdef __STDC__
 
 /* ellipsis is a different case though */
 #ifdef __STDC__
index bbb08c4..ec1118e 100644 (file)
@@ -7,7 +7,7 @@
  */
 
 #ifndef lint
  */
 
 #ifndef lint
-static char sccsid[] = "@(#)util.c     8.54 (Berkeley) %G%";
+static char sccsid[] = "@(#)util.c     8.55 (Berkeley) %G%";
 #endif /* not lint */
 
 # include "sendmail.h"
 #endif /* not lint */
 
 # include "sendmail.h"
@@ -454,6 +454,8 @@ int
 **                     SFF_MUSTOWN -- "uid" must own this file.
 **                     SFF_NOSLINK -- file cannot be a symbolic link.
 **             mode -- mode bits that must match.
 **                     SFF_MUSTOWN -- "uid" must own this file.
 **                     SFF_NOSLINK -- file cannot be a symbolic link.
 **             mode -- mode bits that must match.
+**             st -- if set, points to a stat structure that will
+**                     get the stat info for the file.
 **
 **     Returns:
 **             0 if fn exists, is owned by uid, and matches mode.
 **
 **     Returns:
 **             0 if fn exists, is owned by uid, and matches mode.
@@ -477,14 +479,17 @@ int
 # define S_IXUSR       (S_IEXEC)
 #endif
 
 # define S_IXUSR       (S_IEXEC)
 #endif
 
+#define ST_MODE_NOFILE 0171147         /* unlikely to occur */
+
 int
 int
-safefile(fn, uid, gid, uname, flags, mode)
+safefile(fn, uid, gid, uname, flags, mode, st)
        char *fn;
        uid_t uid;
        gid_t gid;
        char *uname;
        int flags;
        int mode;
        char *fn;
        uid_t uid;
        gid_t gid;
        char *uname;
        int flags;
        int mode;
+       struct stat *st;
 {
        register char *p;
        register struct group *gr = NULL;
 {
        register char *p;
        register struct group *gr = NULL;
@@ -494,6 +499,8 @@ safefile(fn, uid, gid, uname, flags, mode)
                printf("safefile(%s, uid=%d, gid=%d, flags=%x, mode=%o):\n",
                        fn, uid, gid, flags, mode);
        errno = 0;
                printf("safefile(%s, uid=%d, gid=%d, flags=%x, mode=%o):\n",
                        fn, uid, gid, flags, mode);
        errno = 0;
+       if (st == NULL)
+               st = &stbuf;
 
        if (!bitset(SFF_NOPATHCHECK, flags) ||
            (uid == 0 && !bitset(SFF_ROOTOK, flags)))
 
        if (!bitset(SFF_NOPATHCHECK, flags) ||
            (uid == 0 && !bitset(SFF_ROOTOK, flags)))
@@ -548,10 +555,10 @@ safefile(fn, uid, gid, uname, flags, mode)
        }
 
 #ifdef HASLSTAT
        }
 
 #ifdef HASLSTAT
-       if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, &stbuf)
-                                       : stat(fn, &stbuf)) < 0)
+       if ((bitset(SFF_NOSLINK, flags) ? lstat(fn, st)
+                                       : stat(fn, st)) < 0)
 #else
 #else
-       if (stat(fn, &stbuf) < 0)
+       if (stat(fn, st) < 0)
 #endif
        {
                int ret = errno;
 #endif
        {
                int ret = errno;
@@ -560,29 +567,73 @@ safefile(fn, uid, gid, uname, flags, mode)
                        printf("\t%s\n", errstring(ret));
 
                errno = 0;
                        printf("\t%s\n", errstring(ret));
 
                errno = 0;
+               if (!bitset(SFF_CREAT, flags))
+                       return ret;
+
+               /* check to see if legal to create the file */
+               p = strrchr(fn, '/');
+               if (p == NULL)
+                       return ENOTDIR;
+               *p = '\0';
+               if (stat(fn, &stbuf) >= 0)
+               {
+                       int md = S_IWRITE|S_IEXEC;
+                       if (stbuf.st_uid != uid)
+                               md >>= 6;
+                       if ((stbuf.st_mode & md) != md)
+                               errno = EACCES;
+               }
+               ret = errno;
+               if (tTd(54, 4))
+                       printf("\t[final dir %s uid %d mode %o] %s\n",
+                               fn, stbuf.st_uid, stbuf.st_mode,
+                               errstring(ret));
+               *p = '/';
+               st->st_mode = ST_MODE_NOFILE;
                return ret;
        }
 
 #ifdef S_ISLNK
                return ret;
        }
 
 #ifdef S_ISLNK
-       if (bitset(SFF_NOSLINK, flags) && S_ISLNK(stbuf.st_mode))
+       if (bitset(SFF_NOSLINK, flags) && S_ISLNK(st->st_mode))
        {
                if (tTd(54, 4))
        {
                if (tTd(54, 4))
-                       printf("\t[slink mode %o]\tEPERM\n", stbuf.st_mode);
+                       printf("\t[slink mode %o]\tEPERM\n", st->st_mode);
                return EPERM;
        }
 #endif
 
                return EPERM;
        }
 #endif
 
+       if (bitset(S_IWUSR|S_IWGRP|S_IWOTH, mode) && bitset(0111, st->st_mode))
+       {
+               if (tTd(29, 5))
+                       printf("failed (mode %o: x bits)\n", st->st_mode);
+               errno = EPERM;
+               return FALSE;
+       }
+
+       if (bitset(SFF_SETUIDOK, flags))
+       {
+               if (bitset(S_ISUID, st->st_mode) &&
+                   (st->st_uid != 0 || bitset(SFF_ROOTOK, flags)))
+               {
+                       uid = st->st_uid;
+                       uname = NULL;
+               }
+               if (bitset(S_ISGID, st->st_mode) &&
+                   (st->st_gid != 0 || bitset(SFF_ROOTOK, flags)))
+                       gid = st->st_gid;
+       }
+
        if (uid == 0 && !bitset(SFF_ROOTOK, flags))
                mode >>= 6;
        if (uid == 0 && !bitset(SFF_ROOTOK, flags))
                mode >>= 6;
-       else if (stbuf.st_uid != uid)
+       else if (st->st_uid != uid)
        {
                mode >>= 3;
        {
                mode >>= 3;
-               if (stbuf.st_gid == gid)
+               if (st->st_gid == gid)
                        ;
 #ifndef NO_GROUP_SET
                else if (uname != NULL &&
                        ;
 #ifndef NO_GROUP_SET
                else if (uname != NULL &&
-                        ((gr != NULL && gr->gr_gid == stbuf.st_gid) ||
-                         (gr = getgrgid(stbuf.st_gid)) != NULL))
+                        ((gr != NULL && gr->gr_gid == st->st_gid) ||
+                         (gr = getgrgid(st->st_gid)) != NULL))
                {
                        register char **gp;
 
                {
                        register char **gp;
 
@@ -598,10 +649,10 @@ safefile(fn, uid, gid, uname, flags, mode)
        }
        if (tTd(54, 4))
                printf("\t[uid %d, stat %o, mode %o] ",
        }
        if (tTd(54, 4))
                printf("\t[uid %d, stat %o, mode %o] ",
-                       stbuf.st_uid, stbuf.st_mode, mode);
-       if ((stbuf.st_uid == uid || stbuf.st_uid == 0 ||
+                       st->st_uid, st->st_mode, mode);
+       if ((st->st_uid == uid || st->st_uid == 0 ||
             !bitset(SFF_MUSTOWN, flags)) &&
             !bitset(SFF_MUSTOWN, flags)) &&
-           (stbuf.st_mode & mode) == mode)
+           (st->st_mode & mode) == mode)
        {
                if (tTd(54, 4))
                        printf("\tOK\n");
        {
                if (tTd(54, 4))
                        printf("\tOK\n");
@@ -612,6 +663,81 @@ safefile(fn, uid, gid, uname, flags, mode)
        return EACCES;
 }
 \f/*
        return EACCES;
 }
 \f/*
+**  SAFEFOPEN -- do a file open with extra checking
+**
+**     Parameters:
+**             fn -- the file name to open.
+**             omode -- the open-style mode flags.
+**             cmode -- the create-style mode flags.
+**             sff -- safefile flags.
+**
+**     Returns:
+**             Same as fopen.
+*/
+
+FILE *
+safefopen(fn, omode, cmode, sff)
+       char *fn;
+       int omode;
+       int cmode;
+       int sff;
+{
+       int rval;
+       FILE *fp;
+       int smode;
+       struct stat stb, sta;
+       extern char RealUserName[];
+
+       if (bitset(O_CREAT, omode))
+               sff |= SFF_CREAT;
+       smode = 0;
+       switch (omode & O_ACCMODE)
+       {
+         case O_RDONLY:
+               smode = S_IREAD;
+               break;
+
+         case O_WRONLY:
+               smode = S_IWRITE;
+               break;
+
+         case O_RDWR:
+               smode = S_IREAD|S_IWRITE;
+               break;
+
+         default:
+               smode = 0;
+               break;
+       }
+       rval = safefile(fn, RealUid, RealGid, RealUserName, sff, smode, &stb);
+       if (rval != 0)
+       {
+               errno = rval;
+               return NULL;
+       }
+       if (stb.st_mode == ST_MODE_NOFILE)
+               omode |= O_EXCL;
+
+       fp = dfopen(fn, omode, cmode);
+       if (fp == NULL)
+               return NULL;
+       if (bitset(O_EXCL, omode))
+               return fp;
+       if (fstat(fileno(fp), &sta) < 0 ||
+           sta.st_nlink != stb.st_nlink ||
+           sta.st_dev != stb.st_dev ||
+           sta.st_ino != stb.st_ino ||
+           sta.st_uid != stb.st_uid ||
+           sta.st_gid != stb.st_gid)
+       {
+               syserr("554 cannot open: file %s changed after open", fn);
+               errno = EPERM;
+               fclose(fp);
+               return NULL;
+       }
+       return fp;
+}
+\f/*
 **  FIXCRLF -- fix <CR><LF> in line.
 **
 **     Looks for the <CR><LF> combination and turns it into the
 **  FIXCRLF -- fix <CR><LF> in line.
 **
 **     Looks for the <CR><LF> combination and turns it into the