diff options
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.diff | 435 |
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 + -------------------------- + |