----- Original Message -----
From: "Philip Hazel" <ph10@???>
To: <exim-users@???>
Sent: Thursday, February 14, 2002 2:16 PM
Subject: Re: [Exim] Fw: (bugtraq) Exim 3.34 and lower
> I have now analyzed the patch that was posted on bugtraq, and here are my
> comments for information and for the archive. Like Gaul, This long message
is
> divided into three parts:
>
> (1) An executive summary and overview of the posted patches.
> (2) My own patch for the 3 bugs that are recognized, and one that was
> nearby a bogus patch, but had been overlooked.
> (3) More detailed comments on the posted patches.
>
> Executive summary:
>
> Serious bugs found: 1
> Minor bugs found: 2
> Bugs introduced: 4
> Bugs overlooked: 1
> Bogus changes: 8
>
> The serious bug is the ability to crash Exim by calling it with -C
followed by
> an excessively long string. Note that if a non-privileged user did this,
Exim
> had already abdicated its root privilege, so this could not be exploited
by a
> non-admin user.
>
> The minor bugs are places where the lack of string length checking might
have
> caused problems. (I haven't checked to see if it could in practice.)
>
> The other patches seem to be the result of someone looking at calls to
strcpy()
> in the source, and more or less blindly changing them to strncpy().
> Unfortunately, this doesn't always work, because strncpy() is a rather
weird
> function, and people use it in different ways. For a discussion of
strncpy(),
> see David Wheeler's book on security at
>
> http://www.dwheeler.com/secure-programs/
>
> The author of the patch uses strncpy(a,b,n) to mean "copy string b to
buffer a,
> which is of length n". This is actually not sensible if n is a lot larger
than
> the length of string b. The reason it is not sensible is that strncpy()
insists
> on padding out the destination buffer with binary zeros to the length
given.
> So, if you call strncpy(a, "abc", 4096) for example, you waste a lot of
> resources writing 4092 unnecessary zeros, possibly touching memory pages
> which might otherwise have been left alone.
>
> I myself do not use strncpy() in that manner. I use strncpy(a,b,n) to mean
> "copy the first n characters of string b to buffer a". That is, I use it
to
> extract portions of strings. Of course, that doesn't provide any overflow
> protection. My code has to check that the buffer is big enough before
calling
> strncpy(). In a couple of places in the patch, the author has modified my
own
> used of strncpy() by changing the value of n. The effects of these changes
is
> to introduce bugs, because the wrong number of characters will be copied.
>
> It seems to me that a better way to achieve "copy string b to buffer a
with a
> check on not overflowing the buffer size n" is to use the sprintf()
function
> like this: sprintf(a, "%.*s", n-1, b). This copies no more than n-1
characters,
> and adds ONE zero on the end. In fact, there were two instances of this
usage a
> few lines up the code from the point where the serious bug was found. I'd
just
> forgotten to do it right in one case.
>
> The bogus changes were almost entirely strcpy() to strncpy() changes that
were
> unnecessary because the buffer length had been checked in advance. In the
Exim
> 4 source I have added comments at the bogus change points (those that
still
> exist in Exim 4 - some have disappeared) pointing out why those calls to
> strcpy() are OK. This is in the hope that the next auditor will read the
> comments. I haven't bothered to do this for Exim 3.
>
> Despite some of the published patches being bogus and incorrect, I am
grateful
> to those who took the time to run tests on Exim and to examine the code.
It
> isn't easy understanding somebody else's code, and my paradigms and
formatting
> conventions are different to many people's because of my personal
computing
> history. It's not surprising that some false alarms are raised. As long as
they
> don't come in their hundreds, this is fine. It causes me to look and
double
> check, which is no bad thing.
>
> I am about to go away for the weekend to the FOSDEM meeting in Brussels.
When I
> get back next week I'll put out a 3.35 release. There are a very small
number
> of other minor tweaks that have accumulated.
>
> Here follows my patch for Exim 3.34:
>
> --------------------------------------------------------------------------
-----
> *** exim-3.34/src/deliver.c Wed Dec 19 11:50:27 2001
> --- deliver.c Thu Feb 14 10:32:02 2002
> ***************
> *** 4068,4074 ****
> if (slen + len + 4 >= size)
> {
> int oldsize = size;
> ! size *= 2;
> if (!store_extend(h->text, oldsize, size))
> {
> char *newtext = store_get(size);
> --- 4068,4074 ----
> if (slen + len + 4 >= size)
> {
> int oldsize = size;
> ! while (slen + len + 4 >= size) size *= 2;
> if (!store_extend(h->text, oldsize, size))
> {
> char *newtext = store_get(size);
> *** exim-3.34/src/readconf.c Wed Dec 19 11:50:30 2001
> --- readconf.c Thu Feb 14 09:58:23 2002
> ***************
> *** 2240,2246 ****
>
> /* Finally, try the unadorned name */
>
> ! strcpy(big_buffer, config_filename);
> if (config_file == NULL) config_file = fopen(big_buffer, "r");
>
> /* Failure to open the configuration file is a serious disaster. */
> --- 2240,2246 ----
>
> /* Finally, try the unadorned name */
>
> ! sprintf(big_buffer, "%.256s", config_filename);
> if (config_file == NULL) config_file = fopen(big_buffer, "r");
>
> /* Failure to open the configuration file is a serious disaster. */
> *** exim-3.34/src/match.c Wed Dec 19 11:50:29 2001
> --- match.c Thu Feb 14 11:13:49 2002
> ***************
> *** 876,882 ****
> "+caseful" in the list, it restores a caseful copy from the original
address.
> */
>
> ! strcpy(address, origaddress);
> for (p = address + ((caseless || llen < 0)? 0 : llen); *p != 0; p++)
> *p = tolower(*p);
>
> --- 876,882 ----
> "+caseful" in the list, it restores a caseful copy from the original
address.
> */
>
> ! sprintf(address, "%.*s", big_buffer_size-1, origaddress);
> for (p = address + ((caseless || llen < 0)? 0 : llen); *p != 0; p++)
> *p = tolower(*p);
>
> *** exim-3.34/src/tree.c Wed Dec 19 11:50:31 2001
> --- tree.c Thu Feb 14 11:30:35 2002
> ***************
> *** 32,38 ****
> {
> char *p = s + (int)strlen(s);
> while (p > s && p[-1] != '@') p--;
> ! if (p <= s) strcpy(prepared_address, s); else
> {
> char *t = prepared_address;
> char *pp = p - 2;
> --- 32,38 ----
> {
> char *p = s + (int)strlen(s);
> while (p > s && p[-1] != '@') p--;
> ! if (p <= s) sprintf(prepared_address, "%.*s", sizeof(prepared_address) -
1, s);
> {
> char *t = prepared_address;
> char *pp = p - 2;
> --------------------------------------------------------------------------
-----
> --------------------------------------------------------------------------
-----
>
>
>
> Here follows my analysis of the patch that was posted on bugtraq:
>
>
> > diff -Nru exim-3.34/src.old/accept.c exim-3.34/src/accept.c
> > --- exim-3.34/src.old/accept.c Tue Feb 12 13:40:44 2002
> > +++ exim-3.34/src/accept.c Tue Feb 12 13:47:33 2002
> > @@ -1506,7 +1506,7 @@
> >
> > /* Save for comparing with next one */
> >
> > -strcpy(last_message_id, message_id);
> > +strncpy(last_message_id, message_id, MESSAGE_ID_LENGTH); /* Fixed a
one-byte overflow -- Mixter */
> >
> > /* Add the current message id onto the current process info string if
> > it will fit. */
>
> This is not only bogus, but incorrect. The code has just constructed
message_id
> to be of precisely the correct length, and the buffer is also set up to be
the
> correct size. Copying just the message length would fail to copy the
> terminating zero, which is a bug.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/deliver.c exim-3.34/src/deliver.c
> > --- exim-3.34/src.old/deliver.c Tue Feb 12 13:40:44 2002
> > +++ exim-3.34/src/deliver.c Tue Feb 12 14:15:53 2002
> > @@ -3704,7 +3704,7 @@
> > the message size. */
> >
> > deliver_force = forced;
> > -strcpy(message_id, id);
> > +strncpy(message_id, id, MESSAGE_ID_LENGTH);
> > return_count = 0;
> > message_size = 0;
>
> Same comment as above. Not copying the terminating zero might cause
problems.
> One worry is that the id here could come from the command line as part of
a -M
> or -q item. However, the arguments for those items are checked to ensure
that
> they are valid message ids. So we know that the length of id is OK.
> --------------------------------------------------------------------------
-----
>
> > @@ -4083,7 +4083,8 @@
> > slen += 3;
> > }
> >
> > - strcpy(h->text + slen, s);
> > + /* Fixed potential remote vulnerability -- Mixter */
> > + strncpy(h->text + slen, s, size-slen-1);
> > slen += len;
> > }
>
> This is bogus. The destination has been extended, if necessary, to be long
> enough, in the code a few lines above. However, there is a bug in that
code
> which wasn't spotted.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/host.c exim-3.34/src/host.c
> > --- exim-3.34/src.old/host.c Tue Feb 12 13:40:44 2002
> > +++ exim-3.34/src/host.c Tue Feb 12 19:19:52 2002
> > @@ -281,7 +281,7 @@
> > }
> >
> > sender_fullhost =
> > - store_malloc((int)strlen(fullhost) + (int)strlen(rcvhost) + 2);
> > + store_malloc((int)strlen(fullhost) + (int)strlen(rcvhost) + 3);
> > sender_rcvhost = sender_fullhost + (int)strlen(fullhost) + 1;
> > strcpy(sender_fullhost, fullhost);
> > strcpy(sender_rcvhost, rcvhost);
>
> Bogus. I don't understand the patch at all.
> --------------------------------------------------------------------------
-----
>
> > @@ -471,7 +471,7 @@
> >
> > next = store_malloc(sizeof(ip_address_item));
> > next->next = NULL;
> > - strcpy(next->address, s);
> > + strncpy(next->address, s, 46);
> >
> > if (yield == NULL) yield = last = next; else
> > {
>
> Bogus. The ip_address_item structure contains a vector that is large
enough for
> an IPv6 address. We know that "s" is a string representation of an IP
address.
> Therefore, it will fit.
> --------------------------------------------------------------------------
-----
>
> > @@ -571,7 +571,7 @@
> > /* If there is no buffer, put the string into some new store. */
> >
> > if (buffer == NULL) return string_copy(yield);
> > -strcpy(buffer, yield);
> > +strncpy(buffer, yield, 46);
> > return buffer;
> > }
>
> Bogus again. When this function is called with a non-NULL value of buffer,
it
> is known to be long enough to contain an IPv6 address.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/log.c exim-3.34/src/log.c
> > --- exim-3.34/src.old/log.c Tue Feb 12 13:40:44 2002
> > +++ exim-3.34/src/log.c Tue Feb 12 14:37:56 2002
> > @@ -61,6 +61,14 @@
> > if (!syslog_timestamp) s += 20;
> > len = (int)strlen(s);
> >
> > +/* Added safeguard against syslog overflows -- Mixter */
> > +if(len > 4096)
> > +{
> > + len = 4026;
> > + memset(s+4000,0,strlen(s)-4000);
> > + strcat(s, " WARNING: Message cut off!");
> > +}
> > +
> > #ifndef NO_OPENLOG
> > if (!syslog_open)
> > {
>
> This is a weird one. It appears to be truncating messages to syslog when
they
> are more than 4096 characters long. But if the person who generated the
patch
> had read the comments in the source just a few lines earlier, they would
have
> read this:
>
> /* The given string is split into sections according to length, or at
> embedded newlines, and syslogged as a numbered sequence if there is
more
> than one line. */
>
> The macro MAX_SYSLOG_LEN (defined as 1000) controls the length of string
which
> is actually passed to any one syslog call. Longer strings get sent as
several
> syslog calls, and the Exim manual explains all this in detail. So I don't
> understand this 4096 truncation.
>
> Incidentally, if you do want to truncate a string, there's no need to
memset()
> a huge amount of memory to zero. One zero is enough. Also, strcat() is a
> timewaster if you already know where the end of the string is.
> --------------------------------------------------------------------------
-----
>
> > @@ -185,7 +193,7 @@
> > has been cycled, then open the file. The static slot for saving it is
the same
> > size as buffer, and the text has been checked above to fit. */
> >
> > -if (strcmp(name, "main") == 0) strcpy(mainlog_name, buffer);
> > +if (strcmp(name, "main") == 0) strncpy(mainlog_name, buffer,
LOG_NAME_SIZE);
> >
> > /* After a successful open, arrange for automatic closure on exec(). */
>
> Bogus. The size of mainlog_name has been checked.
> --------------------------------------------------------------------------
-----
>
> > @@ -585,7 +593,7 @@
> > {
> > spaceleft = seplen + 1;
> > ptr = log_buffer + LOG_BUFFER_SIZE - spaceleft;
> > - strcpy(ptr - (int)strlen(tmsg), tmsg);
> > + strncpy(ptr - (int)strlen(tmsg), tmsg, spaceleft);
> > }
> > (void)string_format(ptr, spaceleft, separator);
> > while(*ptr) ptr++;
>
> Bogus. There is known to be enough space in the buffer.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/match.c exim-3.34/src/match.c
> > --- exim-3.34/src.old/match.c Tue Feb 12 13:40:45 2002
> > +++ exim-3.34/src/match.c Tue Feb 12 14:39:45 2002
> > @@ -876,7 +876,7 @@
> > "+caseful" in the list, it restores a caseful copy from the original
address.
> > */
> >
> > -strcpy(address, origaddress);
> > +strncpy(address, origaddress, big_buffer_size);
> > for (p = address + ((caseless || llen < 0)? 0 : llen); *p != 0; p++)
> > *p = tolower(*p);
>
> Minor bug. This is indeed safer if the copy is checked. I've rewritten it
> to use sprintf() because it is copying into what might be quite a large
buffer.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/readconf.c exim-3.34/src/readconf.c
> > --- exim-3.34/src.old/readconf.c Tue Feb 12 13:40:45 2002
> > +++ exim-3.34/src/readconf.c Tue Feb 12 14:25:01 2002
> > @@ -356,7 +356,7 @@
> > char *newbuffer;
> > big_buffer_size += BIG_BUFFER_SIZE;
> > newbuffer = store_malloc(big_buffer_size);
> > - strcpy(newbuffer, big_buffer);
> > + strncpy(newbuffer, big_buffer, big_buffer_size-1);
> > store_free(big_buffer);
> > big_buffer = newbuffer;
> > if (fgets(big_buffer+newlen, big_buffer_size-newlen, config_file)
== NULL)
>
> Bogus. We know the data will fit in the new buffer, because we've just got
a
> new buffer that is bigger than the old one, precisely because the old one
is
> too small.
> --------------------------------------------------------------------------
-----
>
> > @@ -440,7 +440,7 @@
> > {
> > int newsize = big_buffer_size + BIG_BUFFER_SIZE;
> > char *newbuffer = store_malloc(newsize);
> > - strcpy(newbuffer, big_buffer);
> > + strncpy(newbuffer, big_buffer, big_buffer_size-1);
> > s = newbuffer + (s - big_buffer);
> > ss = newbuffer + (ss - big_buffer);
> > t = newbuffer + (t - big_buffer);
>
> Bogus again. Same remark.
> --------------------------------------------------------------------------
-----
>
> > @@ -461,7 +461,7 @@
> > memmove(p + replen, pp, ss - pp + 1);
> > ss += moveby;
> > }
> > - strncpy(p, m->replacement, replen);
> > + strncpy(p, m->replacement, replen-2);
> > t = p + replen;
> > }
> > }
>
> This would introduce a bug. The value of replen is the number of
characters to
> be copied. I don't understand the patch.
> --------------------------------------------------------------------------
-----
>
> > @@ -2240,7 +2240,8 @@
> >
> > /* Finally, try the unadorned name */
> >
> > -strcpy(big_buffer, config_filename);
> > +/* Fixed overflow. 256 chars are maximally needed here. -- Mixter */
> > +strncpy(big_buffer, config_filename,
big_buffer_size>256?256:big_buffer_size);
> > if (config_file == NULL) config_file = fopen(big_buffer, "r");
> >
> > /* Failure to open the configuration file is a serious disaster. */
>
> This is the -C bug. My fix uses sprintf() because big_buffer is quite big,
and
> zeroing most of it is pointless.
> --------------------------------------------------------------------------
-----
>
> > @@ -2326,7 +2327,7 @@
> > m->next = NULL;
> > m->command_line = FALSE;
> > if (mlast == NULL) macros = m; else mlast->next = m;
> > - strcpy(m->name, name);
> > + strncpy(m->name, name, namelen-1); /* fixed potential overflow --
Mixter */
> > m->replacement = string_copy(s);
> > }
>
> This is another patch I don't understand. The length of the string that is
> being copied is "namelen". Reducing it by 1 will break the code. The line
> before the ones quoted gets an amount of store which is large enough for
the
> data.
> --------------------------------------------------------------------------
-----
>
> > diff -Nru exim-3.34/src.old/tree.c exim-3.34/src/tree.c
> > --- exim-3.34/src.old/tree.c Tue Feb 12 13:40:46 2002
> > +++ exim-3.34/src/tree.c Tue Feb 12 14:30:45 2002
> > @@ -32,7 +32,7 @@
> > {
> > char *p = s + (int)strlen(s);
> > while (p > s && p[-1] != '@') p--;
> > -if (p <= s) strcpy(prepared_address, s); else
> > +if (p <= s) strncpy(prepared_address, s, 512); else /* fixed potential
remote overflow -- Mixter */
> > {
> > char *t = prepared_address;
> > char *pp = p - 2;
>
> This is another place that could benefit from a doublecheck. I have
changed it
> to use sprintf().
> --------------------------------------------------------------------------
-----
>
> --
> Philip Hazel University of Cambridge Computing Service,
> ph10@??? Cambridge, England. Phone: +44 1223 334714.
>
>
>
>
>