summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--debian/changelog11
-rw-r--r--debian/patches/CVE-2023-51766.patch318
-rw-r--r--debian/patches/series1
3 files changed, 330 insertions, 0 deletions
diff --git a/debian/changelog b/debian/changelog
index 61d75f1..53ca9cb 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
+exim4 (4.92-8+deb10u9) buster-security; urgency=high
+
+ * Non-maintainer upload by the LTS team.
+ * Fix CVE-2023-51766:
+ It was discovered that Exim, a mail transport agent, can be induced to
+ accept a second message embedded as part of the body of a first message in
+ certain configurations where PIPELINING or CHUNKING on incoming connections
+ is offered.
+
+ -- Markus Koschany <apo@debian.org> Thu, 04 Jan 2024 21:07:16 +0100
+
exim4 (4.92-8+deb10u8progress5u1) engywuck-security; urgency=high
* Uploading to engywuck-security, remaining changes:
diff --git a/debian/patches/CVE-2023-51766.patch b/debian/patches/CVE-2023-51766.patch
new file mode 100644
index 0000000..4157fe5
--- /dev/null
+++ b/debian/patches/CVE-2023-51766.patch
@@ -0,0 +1,318 @@
+From: Markus Koschany <apo@debian.org>
+Date: Wed, 27 Dec 2023 14:58:32 +0100
+Subject: CVE-2023-51766
+
+Bug-Debian: https://bugs.debian.org/1059387
+Origin: https://git.exim.org/exim.git/commit/cf1376206284f2a4f11e32d931d4aade34c206c5
+Origin: https://git.exim.org/exim.git/commit/4596719398f6f2365bed563aafd757a6433ce7b4
+Origin: https://git.exim.org/exim.git/commit/5bb786d5ad568a88d50d15452aacc8404047e5ca
+---
+ doc/spec.txt | 7 ++-
+ src/receive.c | 184 ++++++++++++++++++++++++++++++++--------------------------
+ src/smtp_in.c | 8 +--
+ 3 files changed, 111 insertions(+), 88 deletions(-)
+
+diff --git a/doc/spec.txt b/doc/spec.txt
+index 17e0452..330cd24 100644
+--- a/doc/spec.txt
++++ b/doc/spec.txt
+@@ -32028,8 +32028,6 @@ 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.
+
+@@ -32044,7 +32042,10 @@ follows:
+
+ * 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.
+
+
+ 47.3 Unqualified addresses
+diff --git a/src/receive.c b/src/receive.c
+index 227ace0..8870645 100644
+--- a/src/receive.c
++++ b/src/receive.c
+@@ -777,100 +777,114 @@ 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
+@@ -1086,7 +1100,7 @@ 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);
+ }
+
+@@ -1865,8 +1879,10 @@ for (;;)
+
+ 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;
+ }
+
+@@ -1881,8 +1897,13 @@ for (;;)
+
+ 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')
+@@ -1918,7 +1939,8 @@ for (;;)
+ 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;
+ }
+
+@@ -3038,7 +3060,7 @@ if (cutthrough.cctx.sock >= 0 && cutthrough.delivery)
+
+
+ /* 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");
+@@ -3107,7 +3129,7 @@ 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);
+diff --git a/src/smtp_in.c b/src/smtp_in.c
+index 76784c1..88a4f6b 100644
+--- a/src/smtp_in.c
++++ b/src/smtp_in.c
+@@ -5392,12 +5392,12 @@ while (done <= 0)
+ }
+
+ 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;
diff --git a/debian/patches/series b/debian/patches/series
index bab606e..9e714db 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -62,3 +62,4 @@ CVE-2022-37452.patch
use-uschar-more-in-spa-authenticator.patch
CVE-2023-42116.patch
CVE-2023-42114.patch
+CVE-2023-51766.patch