https://nvd.nist.gov/vuln/detail/CVE-2023-51766 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 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 reduced to source changes only for Gentoo diff --git a/src/src/receive.c b/src/src/receive.c index e35400aec..c6f612832 100644 --- a/src/src/receive.c +++ b/src/src/receive.c @@ -836,93 +842,101 @@ */ 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 @@ -1140,7 +1152,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); } @@ -1960,8 +1960,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; } @@ -1977,7 +1980,11 @@ for (;;) if (f.dot_ends && ptr == 0 && ch == '.') { 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') @@ -2013,7 +2020,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; } @@ -3241,7 +3253,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/src/smtp_in.c b/src/src/smtp_in.c index e19c86ff8..aeaffeb37 100644 --- a/src/src/smtp_in.c +++ b/src/src/smtp_in.c @@ -5112,7 +5112,10 @@ while (done <= 0) to get the DATA command sent. */ if (!acl_smtp_predata && cutthrough.cctx.sock < 0) + { + if (!check_sync()) goto SYNC_FAILURE; rc = OK; + } else { uschar * acl = acl_smtp_predata ? acl_smtp_predata : US"accept";