summaryrefslogtreecommitdiffstats
path: root/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/79_CVE-2023-51766_4.97.1-release.diff')
-rw-r--r--debian/patches/79_CVE-2023-51766_4.97.1-release.diff435
1 files changed, 435 insertions, 0 deletions
diff --git a/debian/patches/79_CVE-2023-51766_4.97.1-release.diff b/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
new file mode 100644
index 0000000..6199af9
--- /dev/null
+++ b/debian/patches/79_CVE-2023-51766_4.97.1-release.diff
@@ -0,0 +1,435 @@
+Description: Fix smtp-smuggling (CVE-2023-51766)
+ Pull upstream changes from 4.97.1 security release.
+Author: Jeremy Harris <jgh146exb@wizmail.org>
+Bug-Debian: https://bugs.debian.org/1059387
+Origin: upstream
+Last-Update: 2023-12-31
+
+--- a/doc/ChangeLog
++++ b/doc/ChangeLog
+@@ -229,10 +229,15 @@ JH/53 Bug 2743: fix immediate-delivery v
+
+ JH/57 Fix control=fakreject for a custom message containing tainted data.
+ Previously this resulted in a log complaint, due to a re-expansion present
+ since fakereject was originally introduced.
+
++JH/s1 Refuse to accept a line "dot, LF" as end-of-DATA unless operating in
++ LF-only mode (as detected from the first header line). Previously we did
++ accept that in (normal) CRLF mode; this has been raised as a possible
++ attack scenario (under the name "smtp smuggling", CVE-2023-51766).
++
+
+ Exim version 4.94
+ -----------------
+
+ JH/01 Avoid costly startup code when not strictly needed. This reduces time
+--- /dev/null
++++ b/doc/doc-txt/cve-2023-51766
+@@ -0,0 +1,69 @@
++CVE ID: CVE-2023-51766
++Date: 2016-12-15
++Credits: https://sec-consult.com/blog/detail/smtp-smuggling-spoofing-e-mails-worldwide/
++Version(s): all up to 4.97 inclusive
++Issue: Given a buggy relay, Exim can be induced to accept a second message embedded
++ as part of the body of a first message
++
++Conditions
++==========
++
++If *all* the following conditions are met
++
++ Runtime options
++ ---------------
++
++ * Exim offers PIPELINING on incoming connections
++
++ * Exim offers CHUNKING on incoming connections
++
++ Operation
++ ---------
++
++ * DATA (as opposed to BDAT) is used for a message reception
++
++ * The relay host sends to the Exim MTA message data including
++ one of "LF . LF" or "CR LF . LF" or "LF . CR LF".
++
++ * Exim interprets the sequence as signalling the end of data for
++ the SMTP DATA command, and hence a first message.
++
++ * Exim interprets further input which the relay had as message body
++ data, as SMTP commands and data. This could include a MAIL, RCPT,
++ BDAT (etc) sequence, resulting in a further message acceptance.
++
++Impact
++======
++
++One or more messages can be accepted by Exim that have not been
++properly validated by the buggy relay.
++
++Fix
++===
++
++Install a fixed Exim version:
++
++ 4.98 (once available)
++ 4.97.1
++
++If you can't install one of the above versions, ask your package
++maintainer for a version containing the backported fix. On request and
++depending on our resources we will support you in backporting the fix.
++(Please note, that Exim project officially doesn't support versions
++prior the current stable version.)
++
++
++Workaround
++==========
++
++ Disable CHUNKING advertisement for incoming connections.
++
++ An attempt to "smuggle" a DATA command will trip a syncronisation
++ check.
++
++*or*
++
++ Disable PIPELINING advertisement for incoming connections.
++
++ The "smuggled" MAIL FROM command will then trip a syncronisation
++ check.
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -805,104 +805,118 @@ we make the CRs optional in all cases.
+
+ July 2003: Bare CRs cause trouble. We now treat them as line terminators as
+ well, so that there are no CRs in spooled messages. However, the message
+ terminating dot is not recognized between two bare CRs.
+
++Dec 2023: getting a site to send a body including an "LF . LF" sequence
++followed by SMTP commands is a possible "smtp smuggling" attack. If
++the first (header) line for the message has a proper CRLF then enforce
++that for the body: convert bare LF to a space.
++
+ Arguments:
+- fout a FILE to which to write the message; NULL if skipping
++ fout a FILE to which to write the message; NULL if skipping
++ strict_crlf require full CRLF sequence as a line ending
+
+ Returns: One of the END_xxx values indicating why it stopped reading
+ */
+
+ static int
+-read_message_data_smtp(FILE *fout)
++read_message_data_smtp(FILE * fout, BOOL strict_crlf)
+ {
+-int ch_state = 0;
+-int ch;
+-int linelength = 0;
++enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state =
++ s_linestart;
++int linelength = 0, ch;
+
+ while ((ch = (receive_getc)(GETC_BUFFER_UNLIMITED)) != EOF)
+ {
+ if (ch == 0) body_zerocount++;
+ switch (ch_state)
+ {
+- case 0: /* After LF or CRLF */
+- if (ch == '.')
+- {
+- ch_state = 3;
+- continue; /* Don't ever write . after LF */
+- }
+- ch_state = 1;
++ case s_linestart: /* After LF or CRLF */
++ if (ch == '.')
++ {
++ ch_state = s_had_nl_dot;
++ continue; /* Don't ever write . after LF */
++ }
++ ch_state = s_normal;
+
+- /* Else fall through to handle as normal uschar. */
++ /* Else fall through to handle as normal uschar. */
+
+- case 1: /* Normal state */
+- if (ch == '\n')
+- {
+- ch_state = 0;
+- body_linecount++;
++ case s_normal: /* Normal state */
++ if (ch == '\r')
++ {
++ ch_state = s_had_cr;
++ continue; /* Don't write the CR */
++ }
++ if (ch == '\n') /* Bare LF at end of line */
++ if (strict_crlf)
++ ch = ' '; /* replace LF with space */
++ else
++ { /* treat as line ending */
++ ch_state = s_linestart;
++ body_linecount++;
++ if (linelength > max_received_linelength)
++ max_received_linelength = linelength;
++ linelength = -1;
++ }
++ break;
++
++ case s_had_cr: /* After (unwritten) CR */
++ body_linecount++; /* Any char ends line */
+ if (linelength > max_received_linelength)
+- max_received_linelength = linelength;
++ max_received_linelength = linelength;
+ linelength = -1;
+- }
+- else if (ch == '\r')
+- {
+- ch_state = 2;
+- continue;
+- }
+- break;
++ if (ch == '\n') /* proper CRLF */
++ ch_state = s_linestart;
++ else
++ {
++ message_size++; /* convert the dropped CR to a stored NL */
++ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++ cutthrough_data_put_nl();
++ if (ch == '\r') /* CR; do not write */
++ continue;
++ ch_state = s_normal; /* not LF or CR; process as standard */
++ }
++ break;
+
+- case 2: /* After (unwritten) CR */
+- body_linecount++;
+- if (linelength > max_received_linelength)
+- max_received_linelength = linelength;
+- linelength = -1;
+- if (ch == '\n')
+- {
+- ch_state = 0;
+- }
+- else
+- {
+- message_size++;
+- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+- cutthrough_data_put_nl();
+- if (ch != '\r') ch_state = 1; else continue;
+- }
+- break;
++ case s_had_nl_dot: /* After [CR] LF . */
++ if (ch == '\n') /* [CR] LF . LF */
++ if (strict_crlf)
++ ch = ' '; /* replace LF with space */
++ else
++ return END_DOT;
++ else if (ch == '\r') /* [CR] LF . CR */
++ {
++ ch_state = s_had_dot_cr;
++ continue; /* Don't write the CR */
++ }
++ /* The dot was removed on reaching s_had_nl_dot. For a doubled dot, here,
++ reinstate it to cutthrough. The current ch, dot or not, is passed both to
++ cutthrough and to file below. */
++ else if (ch == '.')
++ {
++ uschar c = ch;
++ cutthrough_data_puts(&c, 1);
++ }
++ ch_state = s_normal;
++ break;
+
+- case 3: /* After [CR] LF . */
+- if (ch == '\n')
+- return END_DOT;
+- if (ch == '\r')
+- {
+- ch_state = 4;
+- continue;
+- }
+- /* The dot was removed at state 3. For a doubled dot, here, reinstate
+- it to cutthrough. The current ch, dot or not, is passed both to cutthrough
+- and to file below. */
+- if (ch == '.')
+- {
+- uschar c= ch;
+- cutthrough_data_puts(&c, 1);
+- }
+- ch_state = 1;
+- break;
++ case s_had_dot_cr: /* After [CR] LF . CR */
++ if (ch == '\n')
++ return END_DOT; /* Preferred termination */
+
+- case 4: /* After [CR] LF . CR */
+- if (ch == '\n') return END_DOT;
+- message_size++;
+- body_linecount++;
+- if (fout != NULL && fputc('\n', fout) == EOF) return END_WERROR;
+- cutthrough_data_put_nl();
+- if (ch == '\r')
+- {
+- ch_state = 2;
+- continue;
+- }
+- ch_state = 1;
+- break;
++ message_size++; /* convert the dropped CR to a stored NL */
++ body_linecount++;
++ if (fout && fputc('\n', fout) == EOF) return END_WERROR;
++ cutthrough_data_put_nl();
++ if (ch == '\r')
++ {
++ ch_state = s_had_cr;
++ continue; /* CR; do not write */
++ }
++ ch_state = s_normal;
++ break;
+ }
+
+ /* Add the character to the spool file, unless skipping; then loop for the
+ next. */
+
+@@ -1114,11 +1128,11 @@ Returns: nothing
+ void
+ receive_swallow_smtp(void)
+ {
+ if (message_ended >= END_NOTENDED)
+ message_ended = chunking_state <= CHUNKING_OFFERED
+- ? read_message_data_smtp(NULL)
++ ? read_message_data_smtp(NULL, FALSE)
+ : read_message_bdat_smtp_wire(NULL);
+ }
+
+
+
+@@ -1899,12 +1913,14 @@ for (;;)
+ LF specially by inserting a white space after it to ensure that the header
+ line is not terminated. */
+
+ if (ch == '\n')
+ {
+- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = FALSE;
+- else if (first_line_ended_crlf) receive_ungetc(' ');
++ if (first_line_ended_crlf == TRUE_UNSET)
++ first_line_ended_crlf = FALSE;
++ else if (first_line_ended_crlf)
++ receive_ungetc(' ');
+ goto EOL;
+ }
+
+ /* This is not the end of the line. If this is SMTP input and this is
+ the first character in the line and it is a "." character, ignore it.
+@@ -1915,12 +1931,17 @@ for (;;)
+ prevent further reading), and break out of the loop, having freed the
+ empty header, and set next = NULL to indicate no data line. */
+
+ if (ptr == 0 && ch == '.' && f.dot_ends)
+ {
++ /* leading dot while in headers-read mode */
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+- if (ch == '\r')
++ if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ )
++ /* dot, LF but we are in CRLF mode. Attack? */
++ ch = ' '; /* replace the LF with a space */
++
++ else if (ch == '\r')
+ {
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+ if (ch != '\n')
+ {
+ receive_ungetc(ch);
+@@ -1952,11 +1973,12 @@ for (;;)
+ if (ch == '\r')
+ {
+ ch = (receive_getc)(GETC_BUFFER_UNLIMITED);
+ if (ch == '\n')
+ {
+- if (first_line_ended_crlf == TRUE_UNSET) first_line_ended_crlf = TRUE;
++ if (first_line_ended_crlf == TRUE_UNSET)
++ first_line_ended_crlf = TRUE;
+ goto EOL;
+ }
+
+ /* Otherwise, put back the character after CR, and turn the bare CR
+ into LF SP. */
+@@ -3084,11 +3106,11 @@ if (cutthrough.cctx.sock >= 0 && cutthro
+ (void) cutthrough_headers_send();
+ }
+
+
+ /* Open a new spool file for the data portion of the message. We need
+-to access it both via a file descriptor and a stream. Try to make the
++to access it both via a file descriptor and a stdio stream. Try to make the
+ directory if it isn't there. */
+
+ spool_name = spool_fname(US"input", message_subdir, message_id, US"-D");
+ DEBUG(D_receive) debug_printf("Data file name: %s\n", spool_name);
+
+@@ -3153,11 +3175,11 @@ message id or "next" line. */
+ if (!ferror(spool_data_file) && !(receive_feof)() && message_ended != END_DOT)
+ {
+ if (smtp_input)
+ {
+ message_ended = chunking_state <= CHUNKING_OFFERED
+- ? read_message_data_smtp(spool_data_file)
++ ? read_message_data_smtp(spool_data_file, first_line_ended_crlf)
+ : spool_wireformat
+ ? read_message_bdat_smtp_wire(spool_data_file)
+ : read_message_bdat_smtp(spool_data_file);
+ receive_linecount++; /* The terminating "." line */
+ }
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5393,16 +5393,16 @@ while (done <= 0)
+ }
+ break;
+ }
+
+ if (chunking_state > CHUNKING_OFFERED)
+- rc = OK; /* No predata ACL or go-ahead output for BDAT */
++ rc = OK; /* There is no predata ACL or go-ahead output for BDAT */
+ else
+ {
+- /* If there is an ACL, re-check the synchronization afterwards, since the
+- ACL may have delayed. To handle cutthrough delivery enforce a dummy call
+- to get the DATA command sent. */
++ /* If there is a predata-ACL, re-check the synchronization afterwards,
++ since the ACL may have delayed. To handle cutthrough delivery enforce a
++ dummy call to get the DATA command sent. */
+
+ if (acl_smtp_predata == NULL && cutthrough.cctx.sock < 0)
+ rc = OK;
+ else
+ {
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -32960,12 +32960,10 @@ MTA within an operating system would use
+ has shown that this is not the case; for example, there are Unix applications
+ that use CRLF in this circumstance. For this reason, and for compatibility with
+ other MTAs, the way Exim handles line endings for all messages is now as
+ follows:
+
+- * LF not preceded by CR is treated as a line ending.
+-
+ * CR is treated as a line ending; if it is immediately followed by LF, the LF
+ is ignored.
+
+ * The sequence "CR, dot, CR" does not terminate an incoming SMTP message, nor
+ a local message in the state where a line containing only a dot is a
+@@ -32976,11 +32974,14 @@ follows:
+ behind this is that bare CRs in header lines are most likely either to be
+ mistakes, or people trying to play silly games.
+
+ * If the first header line received in a message ends with CRLF, a subsequent
+ bare LF in a header line is treated in the same way as a bare CR in a
+- header line.
++ header line and a bare LF in a body line is replaced with a space.
++
++ * If the first header line received in a message does not end with CRLF, a
++ subsequent LF not preceded by CR is treated as a line ending.
+
+
+ 48.3 Unqualified addresses
+ --------------------------
+