========================================================================
CVE-2020-UNGET -- Heap buffer underflow in smtp_ungetc()
========================================================================

Exim calls smtp_refill() to read input characters from an SMTP client
into the 8KB smtp_inbuffer, and calls smtp_getc() to read individual
characters from smtp_inbuffer:

 501 static BOOL
 502 smtp_refill(unsigned lim)
 503 {
 ...
 512 rc = read(fileno(smtp_in), smtp_inbuffer, MIN(IN_BUFFER_SIZE-1, lim));
 ...
 515 if (rc <= 0)
 516   {
 ...
 536   return FALSE;
 537   }
 ...
 541 smtp_inend = smtp_inbuffer + rc;
 542 smtp_inptr = smtp_inbuffer;
 543 return TRUE;
 544 }

 559 int
 560 smtp_getc(unsigned lim)
 561 {
 562 if (smtp_inptr >= smtp_inend)
 563   if (!smtp_refill(lim))
 564     return EOF;
 565 return *smtp_inptr++;
 566 }

Exim implements an smtp_ungetc() function to push characters back into
smtp_inbuffer (characters that were read from smtp_inbuffer by
smtp_getc()):

 795 int
 796 smtp_ungetc(int ch)
 797 {
 798 *--smtp_inptr = ch;
 799 return ch;
 800 }

Unfortunately, Exim also calls smtp_ungetc() to push back "characters"
that were not actually read from smtp_inbuffer: EOF, and (if BDAT is
used) EOD and ERR. For example, in receive_msg():

1945   if (ch == '\r')
1946     {
1947     ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
1948     if (ch == '\n')
1949       {
....
1952       }
....
1957     ch = (receive_ungetc)(ch);

- at line 1947, receive_getc (smtp_getc()) can return EOF;

- at line 1957, this EOF is passed to receive_ungetc (smtp_ungetc());

- at line 798 (in smtp_ungetc()), if smtp_inptr is exactly equal to
  smtp_inbuffer, then it is decremented to smtp_inbuffer - 1, and EOF is
  written out of smtp_inbuffer's bounds.

To return EOF in receive_msg() while smtp_inptr is equal to
smtp_inbuffer, we must initiate a TLS-encrypted connection:

- either through TLS-on-connect (usually on port 465), which does not
  use smtp_inptr nor smtp_inbuffer;

- or through STARTTLS, which resets smtp_inptr to smtp_inbuffer in the
  following code (if X_PIPE_CONNECT is enabled, the default since Exim
  4.94):

5484       if (receive_smtp_buffered())
5485         {
5486         DEBUG(D_any)
5487           debug_printf("Non-empty input buffer after STARTTLS; naive attack?\n");
5488         if (tls_in.active.sock < 0)
5489           smtp_inend = smtp_inptr = smtp_inbuffer;

In both cases:

- first, we initiate a TLS-encrypted connection, which sets receive_getc
  and receive_ungetc to tls_getc() and tls_ungetc() (while smtp_inptr is
  equal to smtp_inbuffer);

- second, we start a mail (MAIL FROM, RCPT TO, and DATA commands) and
  enter receive_msg();

- third, we send a bare '\r' character and reach line 1945;

- next, we terminate the TLS connection, which resets receive_getc and
  receive_ungetc to smtp_getc() and smtp_ungetc() (while smtp_inptr is
  still equal to smtp_inbuffer);

- last, we close the underlying TCP connection, which returns EOF at
  line 1947 and writes EOF out of smtp_inbuffer's bounds at line 1957
  (line 798 in smtp_ungetc()).

We have not tried to exploit this vulnerability; if exploitable, it
would allow an unauthenticated remote attacker to execute arbitrary
commands as the "exim" user (if TLS and either TLS-on-connect or
X_PIPE_CONNECT are enabled).

