COMMAND
sendmail's mail.local
SYSTEMS AFFECTED
Sendmail (see below)
PROBLEM
3APA3A found following. There are 4 problems:
1. Possibility to insert LMTP commands into e-mail message
2. Possibility of deadlock between sendmail and mail.local
3. Possibility to corrupt user's mailbox
4. Possibility to change e-mail headers of the message in
user's mailbox
Vulnerable software:
- Problems 1 and 2: sendmail before 8.10.0 (8.9.3 tested), all
platforms
- Problems 3 and 4: sendmail 8.10.0 and 8.10.1 (8.10.1 tested)
under Solaris only
Problem 1:
==========
While in LMTP mode mail.local checks input for ".\n" string to
find the end of the message. Sendmail will not allow this string
to pass through, but using of fgets() with buffersize 2048 allows
to emulate end of the message with a string
(2047 chars).\n
The rest of the message will be treated as LMTP commands. This
allows to send messages to multiple mailboxes (including private
and closed ones) with any spoofed information bypassing sendmail
and without any filtering, checking, logging etc.
Problem 2:
==========
In the conditions of the problem 1, mail.local will return LMTP
answers to sendmail. Because sendmail doesn't expects any output
from mail.local at this point, replies will not be taken from I/O
buffer. If extreamaly large number of LMTP commands is used in
the text, or the rest of the message is just a collection of text
strings (in this case mail.local will reply with a error for every
string) sendmail and mail.local will be deadlocked then the buffer
will be filled. This deadlock problem regardless to LMTP command
execution was reported to sendmail team by Peter Jeremy and
patched in 8.10.0 release.
Problem 3:
==========
sendmail 8.10.0 introduces support for "Content-Length: " header
for Solaris in mail.local. This header stores the size of the
message and is used by mail retrieving software (f.e. qpopper) to
calculate message boundary in the mailbox. If this header already
exists in message it will be stripped, but because fgets() is used
for parsing message it's possible to add "Content-Length:
99999999" header and comment out real "Content-Length: " causing
mailbox corruption using strings
(2047 chars)\n
Content-Length: 99999999\n
in the end of message header.
Problem 4:
==========
If message body is empty and last string of the message header is
(2047 chars)Content-Length: \n
the headers of this message will be glued to the next message in
mailbox, because '\n' character will be lost while stripping
Content-Length.
SOLUTION
Vendor contacted, problems 1 and 2 are patched in sendmail 8.10.0.
The patch below will be in the next release of sendmail:
Index: mail.local.c
===================================================================
RCS file: /cvs/mail.local/mail.local.c,v
retrieving revision 8.145
retrieving revision 8.146
diff -u -r8.145 -r8.146
--- mail.local.c 2000/04/25 15:27:08 8.145
+++ mail.local.c 2000/04/25 15:48:32 8.146
@@ -746,7 +746,8 @@
FILE *fp = NULL;
time_t tval;
bool eline;
- bool fullline = TRUE;
+ bool fullline = TRUE; /* current line is terminated */
+ bool prevfl; /* previous line was terminated */
char line[2048];
int fd;
char tmpbuf[sizeof _PATH_LOCTMP + 1];
@@ -783,16 +784,19 @@
#endif /* CONTENTLENGTH */
line[0] = '\0';
- for (eline = TRUE; fgets(line, sizeof(line), stdin); )
+ eline = TRUE;
+ while (fgets(line, sizeof(line), stdin) != (char *)NULL)
{
size_t line_len = 0;
int peek;
+
+ prevfl = fullline; /* preserve state of previous line */
while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
line_len++;
line_len++;
/* Check for dot-stuffing */
- if (fullline && lmtprcpts && line[0] == '.')
+ if (prevfl && lmtprcpts && line[0] == '.')
{
if (line[1] == '\n' ||
(line[1] == '\r' && line[2] == '\n'))
@@ -801,7 +805,7 @@
line_len--;
}
- /* Check to see if we have the full line from the fgets() */
+ /* Check to see if we have the full line from fgets() */
fullline = FALSE;
if (line_len > 0)
{
@@ -809,12 +813,11 @@
{
if (line_len >= 2 &&
line[line_len - 2] == '\r')
- {
- (void) strlcpy(line + line_len - 2,
- "\n", sizeof line -
- line_len + 2);
+ {
+ line[line_len - 2] = '\n';
+ line[line_len - 1] = '\0';
line_len--;
- }
+ }
fullline = TRUE;
}
else if (line[line_len - 1] == '\r')
@@ -834,7 +837,7 @@
fullline = TRUE;
#ifdef CONTENTLENGTH
- if (line[0] == '\n' && HeaderLength == 0)
+ if (prevfl && line[0] == '\n' && HeaderLength == 0)
{
eline = FALSE;
HeaderLength = ftell(fp);
@@ -849,7 +852,7 @@
}
}
#else /* CONTENTLENGTH */
- if (line[0] == '\n')
+ if (prevfl && line[0] == '\n')
eline = TRUE;
#endif /* CONTENTLENGTH */
else
@@ -860,10 +863,17 @@
eline = FALSE;
#ifdef CONTENTLENGTH
/* discard existing "Content-Length:" headers */
- if (HeaderLength == 0 &&
+ if (prevfl && HeaderLength == 0 &&
(line[0] == 'C' || line[0] == 'c') &&
strncasecmp(line, ContentHdr, 15) == 0)
- continue;
+ {
+ /*
+ ** be paranoid: clear the line
+ ** so no "wrong matches" may occur later
+ */
+ line[0] = '\0';
+ continue;
+ }
#endif /* CONTENTLENGTH */
}