[LWN Logo]

Date:	Wed, 20 Jan 1999 21:56:57 -0800
From:	Gregory Neil Shapiro <sendmail+gshapiro@SENDMAIL.ORG>
Subject:      Sendmail 8.8.x/8.9.x bugware
To:	BUGTRAQ@NETSPACE.ORG

-----BEGIN PGP SIGNED MESSAGE-----

Michal> 1. Redirection attack

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

Working with Michal, I believe we have concluded that this is not bug in
8.9.2 but rather a policy decision made by some sites running 8.9.2.
Specifically, sendmail includes a method for tuning the anti-relaying
rulesets via an access database or other features.  Sites can also
completely turn off the anti-relaying checks and leave themselves wide open
to attacks.

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.

Michal> 2. 'Headers prescan' DoS

Michal> There are possible DoS attacks due to ineffective headers prescan
Michal> algorithm. Two or three medium-size (200 kb) mail messages may
Michal> render system unusable for quite long period of time (as headers
Michal> are parsed at least twice, on message collection and in
Michal> queue). Exploit sold separately :-)

While studying the denial of service attack found by Michal Zalewski, we
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 (available from ftp://ftp.sendmail.org/pub/sendmail/) and
follow these steps:

cd sendmail-8.9.2
patch -p0 < 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.

Questions can be mailed to sendmail-questions@sendmail.org.  Bugs should be
mailed to sendmail-bugs@sendmail.org.

Since this message has been PGP signed, some of the lines below will be
encapsulated by prepending "- " to lines starting with "-" as specified by
RFC 934.  Modern versions of patch will properly deal with this
encapsulation.  If you are using a patch that does not handle this, you may
need to hand edit the patch after saving it.

*** -   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   #


-----BEGIN PGP SIGNATURE-----
Version: 2.6.3a
Charset: noconv

iQCVAwUBNqa7aXxLZ22gDhVjAQHSMwP+KFbI5hxzkeTdV2gptoqHiAf49AT2KqO+
Mu9cIa/OMuYQSnxoBBJZoZqV6e7BR92x9NJOpeVqemzV7iVZNwYrjgzt/5+K5I6V
9J+g43uXk7Es7r6Y50YktLa+yXXD9qGEQf/VgevYTwT+vqo/VYu1cAPFO5kwy7Er
MS4FMoX6SuY=
=Yj/b
-----END PGP SIGNATURE-----