diff -Nru exim4-4.95/debian/changelog exim4-4.95/debian/changelog --- exim4-4.95/debian/changelog 2023-10-25 00:36:57.000000000 +0000 +++ exim4-4.95/debian/changelog 2024-01-11 13:16:58.000000000 +0000 @@ -1,3 +1,15 @@ +exim4 (4.95-4ubuntu2.5) jammy-security; urgency=medium + + * SECURITY UPDATE: SMTP smuggling + - debian/patches/CVE-2023-51766-1.patch: Reject "dot, LF" as + ending data phase in src/receive.c, src/smtp_in.c. + - debian/patches/CVE-2023-51766-2.patch: use enum for body data + input state-machine in src/receive.c. + - debian/patches/CVE-2023-51766-3.patch: fix in src/receive.c. + - CVE-2023-51766 + + -- Leonidas Da Silva Barbosa Thu, 11 Jan 2024 10:16:58 -0300 + exim4 (4.95-4ubuntu2.4) jammy-security; urgency=medium * SECURITY UPDATE: remote code execution diff -Nru exim4-4.95/debian/patches/CVE-2023-51766-1.patch exim4-4.95/debian/patches/CVE-2023-51766-1.patch --- exim4-4.95/debian/patches/CVE-2023-51766-1.patch 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.95/debian/patches/CVE-2023-51766-1.patch 2024-01-11 13:16:25.000000000 +0000 @@ -0,0 +1,79 @@ +From cf1376206284f2a4f11e32d931d4aade34c206c5 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Fri, 22 Dec 2023 23:57:05 +0000 +Subject: [PATCH] Reject "dot, LF" as ending data phase. Bug 3063 +diff --git a/src/receive.c b/src/receive.c +index ff93efd..cc3c78b 100644 +--- a/src/receive.c ++++ b/src/receive.c +@@ -1927,8 +1927,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; + } + +@@ -1937,6 +1939,7 @@ for (;;) + This implements the dot-doubling rule, though header lines starting with + dots aren't exactly common. They are legal in RFC 822, though. If the + following is CRLF or LF, this is the line that that terminates the ++ + entire message. We set message_ended to indicate this has happened (to + prevent further reading), and break out of the loop, having freed the + empty header, and set next = NULL to indicate no data line. */ +@@ -1944,7 +1947,11 @@ for (;;) + if (ptr == 0 && ch == '.' && f.dot_ends) + { + 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') +@@ -1980,7 +1987,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; + } + +diff --git a/src/smtp_in.c b/src/smtp_in.c +index 3612acb..f8f821c 100644 +--- a/src/smtp_in.c ++++ b/src/smtp_in.c +@@ -5443,15 +5443,18 @@ 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) ++ { ++ if (!check_sync()) goto SYNC_FAILURE; + rc = OK; ++ } + else + { + uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept"; diff -Nru exim4-4.95/debian/patches/CVE-2023-51766-2.patch exim4-4.95/debian/patches/CVE-2023-51766-2.patch --- exim4-4.95/debian/patches/CVE-2023-51766-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.95/debian/patches/CVE-2023-51766-2.patch 2024-01-11 13:16:41.000000000 +0000 @@ -0,0 +1,194 @@ +From 4596719398f6f2365bed563aafd757a6433ce7b4 Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 23 Dec 2023 15:59:53 +0000 +Subject: [PATCH] Use enum for body data input state-machine +diff --git a/src/receive.c b/src/receive.c +index cc3c78b..70e3b5a 100644 +--- a/src/receive.c ++++ b/src/receive.c +@@ -839,93 +839,95 @@ 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) + { +-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 NL ends line */ ++ { ++ 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 */ ++ return END_DOT; ++ 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. */ ++ 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 +@@ -1946,6 +1948,7 @@ for (;;) + + if (ptr == 0 && ch == '.' && f.dot_ends) + { ++ /* leading dot while in headers-read mode */ + ch = (receive_getc)(GETC_BUFFER_UNLIMITED); + if (ch == '\n' && first_line_ended_crlf == TRUE /* and not TRUE_UNSET */ ) + /* dot, LF but we are in CRLF mode. Attack? */ +@@ -3120,7 +3123,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"); diff -Nru exim4-4.95/debian/patches/CVE-2023-51766-3.patch exim4-4.95/debian/patches/CVE-2023-51766-3.patch --- exim4-4.95/debian/patches/CVE-2023-51766-3.patch 1970-01-01 00:00:00.000000000 +0000 +++ exim4-4.95/debian/patches/CVE-2023-51766-3.patch 2024-01-11 13:16:51.000000000 +0000 @@ -0,0 +1,99 @@ + +From 5bb786d5ad568a88d50d15452aacc8404047e5ca Mon Sep 17 00:00:00 2001 +From: Jeremy Harris +Date: Sat, 23 Dec 2023 17:42:57 +0000 +Subject: [PATCH] Reject "dot, LF" as ending data phase (pt. 2). Bug 3063 +Index: exim4-4.95/src/receive.c +=================================================================== +--- exim4-4.95.orig/src/receive.c ++++ exim4-4.95/src/receive.c +@@ -832,14 +832,20 @@ July 2003: Bare CRs cause trouble. We no + 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) + { + enum { s_linestart, s_normal, s_had_cr, s_had_nl_dot, s_had_dot_cr } ch_state = + s_linestart; +@@ -866,14 +872,17 @@ while ((ch = (receive_getc)(GETC_BUFFER_ + ch_state = s_had_cr; + continue; /* Don't write the CR */ + } +- if (ch == '\n') /* Bare NL ends line */ +- { +- ch_state = s_linestart; +- body_linecount++; +- if (linelength > max_received_linelength) +- max_received_linelength = linelength; +- linelength = -1; +- } ++ 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 */ +@@ -896,8 +905,11 @@ while ((ch = (receive_getc)(GETC_BUFFER_ + + case s_had_nl_dot: /* After [CR] LF . */ + if (ch == '\n') /* [CR] LF . LF */ +- return END_DOT; +- if (ch == '\r') /* [CR] LF . CR */ ++ 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 */ +@@ -905,7 +917,7 @@ while ((ch = (receive_getc)(GETC_BUFFER_ + /* 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. */ +- if (ch == '.') ++ else if (ch == '.') + { + uschar c = ch; + cutthrough_data_puts(&c, 1); +@@ -1143,7 +1155,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); + } + +@@ -3192,7 +3204,7 @@ if (!ferror(spool_data_file) && !(receiv + 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 -Nru exim4-4.95/debian/patches/series exim4-4.95/debian/patches/series --- exim4-4.95/debian/patches/series 2023-10-25 00:36:57.000000000 +0000 +++ exim4-4.95/debian/patches/series 2024-01-11 13:16:46.000000000 +0000 @@ -28,3 +28,6 @@ CVE-2023-42116.patch CVE-2023-42117.patch CVE-2023-42119.patch +CVE-2023-51766-1.patch +CVE-2023-51766-2.patch +CVE-2023-51766-3.patch