Prereq: "3.7.1" diff -ur --new-file /var/tmp/postfix-3.7.1/src/global/mail_version.h ./src/global/mail_version.h --- /var/tmp/postfix-3.7.1/src/global/mail_version.h 2022-04-18 12:28:28.000000000 -0400 +++ ./src/global/mail_version.h 2022-04-27 19:39:05.000000000 -0400 @@ -20,8 +20,8 @@ * Patches change both the patchlevel and the release date. Snapshots have no * patchlevel; they change the release date only. */ -#define MAIL_RELEASE_DATE "20220418" -#define MAIL_VERSION_NUMBER "3.7.1" +#define MAIL_RELEASE_DATE "20220427" +#define MAIL_VERSION_NUMBER "3.7.2" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff -ur --new-file /var/tmp/postfix-3.7.1/HISTORY ./HISTORY --- /var/tmp/postfix-3.7.1/HISTORY 2022-04-18 11:38:13.000000000 -0400 +++ ./HISTORY 2022-04-27 19:36:44.000000000 -0400 @@ -26327,3 +26327,30 @@ chroot jail. Problem reported by Jesper Dybdal. Files: cleanup/cleanup.h, cleanup/cleanup_init.c, cleanup/cleanup_milter.c, cleanup/cleanup_state.c. + +20220421 + + Bugfix (introduced: Postfix 3.7): reverted an overly complex + change in the postscreen SMTP engine from 20211023, and + replaced it with a much simpler change. The bad change was + segfaulting on some systems after receiving malformed input + (for example, TLS "hello"). File: postscreen/postscreen_smtpd.c. + + Under conditions described below, the postscreen program + attempted to read through an uninitialized 'const' pointer. + The pointer value depended on the compiler type and compiler + options, but crucially, it did not depend on network inputs. + + The conditions were that SMTPUTF8 support was enabled (the + default), and that postscreen received non-UTF8 input, for + example, a TLS or RDP handshake request. Depending on + compiler details, the result of the read operation could + be uninteresting, a combined memory leak and file handle + leak, or a segmentation violation (signal 11). + + The segmentation violation result was reported by Michael + Grimm who used a FreeBSD 13.1 early version. The result was + "uninteresting" with FreeBSD 13.0. Both FreeBSD systems use + Clang instead of GCC. The result was also "uninteresting" + on Linux-based systems that use GCC, or on a few older + systems that use GCC. diff -ur --new-file /var/tmp/postfix-3.7.1/src/postscreen/postscreen_smtpd.c ./src/postscreen/postscreen_smtpd.c --- /var/tmp/postfix-3.7.1/src/postscreen/postscreen_smtpd.c 2021-12-19 08:04:50.000000000 -0500 +++ ./src/postscreen/postscreen_smtpd.c 2022-04-21 19:15:35.000000000 -0400 @@ -794,7 +794,6 @@ char *command; const PSC_SMTPD_COMMAND *cmdp; int write_stat; - int skip_command_processing; if (msg_verbose > 1) msg_info("%s: sq=%d cq=%d event %d on smtp socket %d from [%s]:%s flags=%s", @@ -930,39 +929,31 @@ } /* - * As in smtpd(8), reject malformed UTF-8 when "smtputf8_enable = - * yes". This also avoids noisy "non-UTF-8 key" warnings from - * dict_utf8 infrastructure. - * - * Caution: do not skip all code in the remainder of this loop. + * Avoid complaints from Postfix maps about malformed content. */ - if ((skip_command_processing = (var_smtputf8_enable - && !valid_utf8_string(STR(state->cmd_buffer), - LEN(state->cmd_buffer))))) { - write_stat = PSC_SEND_REPLY(state, - "500 5.5.2 Error: bad UTF-8 syntax"); - } else { +#define PSC_BAD_UTF8(str, len) \ + (var_smtputf8_enable && !valid_utf8_string((str), (len))) - /* - * Terminate the command buffer, and apply the last-resort - * command editing workaround. - */ - VSTRING_TERMINATE(state->cmd_buffer); - if (psc_cmd_filter != 0) { - const char *cp; - - for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++) - /* void */ ; - if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0) { - msg_info("[%s]:%s: replacing command \"%.100s\" with \"%.100s\"", - state->smtp_client_addr, state->smtp_client_port, - STR(state->cmd_buffer), cp); - vstring_strcpy(state->cmd_buffer, cp); - } else if (psc_cmd_filter->error != 0) { - msg_fatal("%s:%s lookup error for \"%.100s\"", - psc_cmd_filter->type, psc_cmd_filter->name, - STR(state->cmd_buffer)); - } + /* + * Terminate the command buffer, and apply the last-resort command + * editing workaround. + */ + VSTRING_TERMINATE(state->cmd_buffer); + if (psc_cmd_filter != 0 && !PSC_BAD_UTF8(STR(state->cmd_buffer), + LEN(state->cmd_buffer))) { + const char *cp; + + for (cp = STR(state->cmd_buffer); *cp && IS_SPACE_TAB(*cp); cp++) + /* void */ ; + if ((cp = psc_dict_get(psc_cmd_filter, cp)) != 0) { + msg_info("[%s]:%s: replacing command \"%.100s\" with \"%.100s\"", + state->smtp_client_addr, state->smtp_client_port, + STR(state->cmd_buffer), cp); + vstring_strcpy(state->cmd_buffer, cp); + } else if (psc_cmd_filter->error != 0) { + msg_fatal("%s:%s lookup error for \"%.100s\"", + psc_cmd_filter->type, psc_cmd_filter->name, + STR(state->cmd_buffer)); } } @@ -975,180 +966,175 @@ state->read_state = PSC_SMTPD_CMD_ST_ANY; VSTRING_RESET(state->cmd_buffer); - if (skip_command_processing == 0) { - - /* - * Process the command line. - * - * Caution: some command handlers terminate the session and destroy - * the session state structure. When this happens we must leave - * the SMTP engine to avoid a dangling pointer problem. - */ - cmd_buffer_ptr = STR(state->cmd_buffer); - if (msg_verbose) - msg_info("< [%s]:%s: %s", state->smtp_client_addr, - state->smtp_client_port, cmd_buffer_ptr); - - /* Parse the command name. */ - if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0) - command = ""; + /* + * Process the command line. + * + * Caution: some command handlers terminate the session and destroy the + * session state structure. When this happens we must leave the SMTP + * engine to avoid a dangling pointer problem. + */ + cmd_buffer_ptr = STR(state->cmd_buffer); + if (msg_verbose) + msg_info("< [%s]:%s: %s", state->smtp_client_addr, + state->smtp_client_port, cmd_buffer_ptr); + + /* Parse the command name. */ + if ((command = PSC_SMTPD_NEXT_TOKEN(cmd_buffer_ptr)) == 0) + command = ""; - /* - * The non-SMTP, PIPELINING and command COUNT tests depend on the - * client command handler. - * - * Caution: cmdp->name and cmdp->action may be null on loop exit. - */ - saved_where = state->where; - state->where = PSC_SMTPD_CMD_UNIMPL; - for (cmdp = command_table; cmdp->name != 0; cmdp++) { - if (strcasecmp(command, cmdp->name) == 0) { - state->where = cmdp->name; - break; - } + /* + * The non-SMTP, PIPELINING and command COUNT tests depend on the + * client command handler. + * + * Caution: cmdp->name and cmdp->action may be null on loop exit. + */ + saved_where = state->where; + state->where = PSC_SMTPD_CMD_UNIMPL; + for (cmdp = command_table; cmdp->name != 0; cmdp++) { + if (strcasecmp(command, cmdp->name) == 0) { + state->where = cmdp->name; + break; } + } - if ((state->flags & PSC_STATE_FLAG_SMTPD_X21) - && cmdp->action != psc_quit_cmd) { - PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, - state->final_reply); + if ((state->flags & PSC_STATE_FLAG_SMTPD_X21) + && cmdp->action != psc_quit_cmd) { + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + state->final_reply); + return; + } + /* Non-SMTP command test. */ + if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP) + == PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0 + && (is_header(command) + || PSC_BAD_UTF8(command, strlen(command)) + /* Ignore forbid_cmds lookup errors. Non-critical feature. */ + || (*var_psc_forbid_cmds + && string_list_match(psc_forbid_cmds, command)))) { + printable(command, '?'); + PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr, + strlen(cmd_buffer_ptr), 100); + msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s", + PSC_CLIENT_ADDR_PORT(state), saved_where, + command, STR(psc_temp)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL); + PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS); + expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */ + /* Skip this test for the remainder of this SMTP session. */ + PSC_SKIP_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_SKIP); + switch (psc_nsmtp_action) { + case PSC_ACT_DROP: + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, + "521 5.7.0 Error: I can break rules, too. Goodbye.\r\n"); return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, + PSC_STATE_FLAG_NSMTP_FAIL); + /* Temporarily allowlist until something else expires. */ + PSC_PASS_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_PASS); + expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl; + break; + default: + msg_panic("%s: unknown non_smtp_command action value %d", + myname, psc_nsmtp_action); } - /* Non-SMTP command test. */ - if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_SKIP) - == PSC_STATE_FLAG_NSMTP_TODO && cmdp->name == 0 - && (is_header(command) - /* Ignore forbid_cmds lookup errors. Non-critical feature. */ - || (*var_psc_forbid_cmds - && string_list_match(psc_forbid_cmds, command)))) { - printable(command, '?'); - PSC_SMTPD_ESCAPE_TEXT(psc_temp, cmd_buffer_ptr, - strlen(cmd_buffer_ptr), 100); - msg_info("NON-SMTP COMMAND from [%s]:%s after %s: %.100s %s", - PSC_CLIENT_ADDR_PORT(state), saved_where, - command, STR(psc_temp)); - PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_FAIL); - PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_NSMTP_PASS); - expire_time[PSC_TINDX_NSMTP] = PSC_TIME_STAMP_DISABLED; /* XXX */ - /* Skip this test for the remainder of this SMTP session. */ - PSC_SKIP_SESSION_STATE(state, "non-smtp test", - PSC_STATE_FLAG_NSMTP_SKIP); - switch (psc_nsmtp_action) { - case PSC_ACT_DROP: - PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, - psc_smtpd_time_event, - "521 5.7.0 Error: I can break rules, too. Goodbye.\r\n"); - return; - case PSC_ACT_ENFORCE: - PSC_ENFORCE_SESSION_STATE(state, - "550 5.5.1 Protocol error\r\n"); - break; - case PSC_ACT_IGNORE: - PSC_UNFAIL_SESSION_STATE(state, - PSC_STATE_FLAG_NSMTP_FAIL); - /* Temporarily allowlist until something else expires. */ - PSC_PASS_SESSION_STATE(state, "non-smtp test", - PSC_STATE_FLAG_NSMTP_PASS); - expire_time[PSC_TINDX_NSMTP] = event_time() + psc_min_ttl; - break; - default: - msg_panic("%s: unknown non_smtp_command action value %d", - myname, psc_nsmtp_action); - } - } - /* Command PIPELINING test. */ - if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0 - && (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP) + } + /* Command PIPELINING test. */ + if ((cmdp->flags & PSC_SMTPD_CMD_FLAG_HAS_PAYLOAD) == 0 + && (state->flags & PSC_STATE_MASK_PIPEL_TODO_SKIP) == PSC_STATE_FLAG_PIPEL_TODO && !PSC_SMTPD_BUFFER_EMPTY(state)) { - printable(command, '?'); - PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state), - PSC_SMTPD_PEEK_LEN(state), 100); - msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s", - PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp)); - PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL); - PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS); - expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */ - /* Skip this test for the remainder of this SMTP session. */ - PSC_SKIP_SESSION_STATE(state, "pipelining test", - PSC_STATE_FLAG_PIPEL_SKIP); - switch (psc_pipel_action) { - case PSC_ACT_DROP: - PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, - psc_smtpd_time_event, + printable(command, '?'); + PSC_SMTPD_ESCAPE_TEXT(psc_temp, PSC_SMTPD_PEEK_DATA(state), + PSC_SMTPD_PEEK_LEN(state), 100); + msg_info("COMMAND PIPELINING from [%s]:%s after %.100s: %s", + PSC_CLIENT_ADDR_PORT(state), command, STR(psc_temp)); + PSC_FAIL_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_FAIL); + PSC_UNPASS_SESSION_STATE(state, PSC_STATE_FLAG_PIPEL_PASS); + expire_time[PSC_TINDX_PIPEL] = PSC_TIME_STAMP_DISABLED; /* XXX */ + /* Skip this test for the remainder of this SMTP session. */ + PSC_SKIP_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_SKIP); + switch (psc_pipel_action) { + case PSC_ACT_DROP: + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, + psc_smtpd_time_event, "521 5.5.1 Protocol error\r\n"); - return; - case PSC_ACT_ENFORCE: - PSC_ENFORCE_SESSION_STATE(state, - "550 5.5.1 Protocol error\r\n"); - break; - case PSC_ACT_IGNORE: - PSC_UNFAIL_SESSION_STATE(state, - PSC_STATE_FLAG_PIPEL_FAIL); - /* Temporarily allowlist until something else expires. */ - PSC_PASS_SESSION_STATE(state, "pipelining test", - PSC_STATE_FLAG_PIPEL_PASS); - expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl; - break; - default: - msg_panic("%s: unknown pipelining action value %d", - myname, psc_pipel_action); - } + return; + case PSC_ACT_ENFORCE: + PSC_ENFORCE_SESSION_STATE(state, + "550 5.5.1 Protocol error\r\n"); + break; + case PSC_ACT_IGNORE: + PSC_UNFAIL_SESSION_STATE(state, + PSC_STATE_FLAG_PIPEL_FAIL); + /* Temporarily allowlist until something else expires. */ + PSC_PASS_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_PASS); + expire_time[PSC_TINDX_PIPEL] = event_time() + psc_min_ttl; + break; + default: + msg_panic("%s: unknown pipelining action value %d", + myname, psc_pipel_action); } + } - /* - * The following tests don't pass until the client gets all the - * way to the RCPT TO command. However, the client can still fail - * these tests with some later command. - */ - if (cmdp->action == psc_rcpt_cmd) { - if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL) - == PSC_STATE_FLAG_BARLF_TODO) { - PSC_PASS_SESSION_STATE(state, "bare newline test", - PSC_STATE_FLAG_BARLF_PASS); - /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ - expire_time[PSC_TINDX_BARLF] = event_time() - + var_psc_barlf_ttl; - } - if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL) - == PSC_STATE_FLAG_NSMTP_TODO) { - PSC_PASS_SESSION_STATE(state, "non-smtp test", - PSC_STATE_FLAG_NSMTP_PASS); - /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ - expire_time[PSC_TINDX_NSMTP] = event_time() - + var_psc_nsmtp_ttl; - } - if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL) - == PSC_STATE_FLAG_PIPEL_TODO) { - PSC_PASS_SESSION_STATE(state, "pipelining test", - PSC_STATE_FLAG_PIPEL_PASS); - /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ - expire_time[PSC_TINDX_PIPEL] = event_time() - + var_psc_pipel_ttl; - } - } - /* Command COUNT limit test. */ - if (++state->command_count > var_psc_cmd_count - && cmdp->action != psc_quit_cmd) { - msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s", - PSC_CLIENT_ADDR_PORT(state), saved_where); - PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, - psc_smtpd_421_reply); - return; + /* + * The following tests don't pass until the client gets all the way + * to the RCPT TO command. However, the client can still fail these + * tests with some later command. + */ + if (cmdp->action == psc_rcpt_cmd) { + if ((state->flags & PSC_STATE_MASK_BARLF_TODO_PASS_FAIL) + == PSC_STATE_FLAG_BARLF_TODO) { + PSC_PASS_SESSION_STATE(state, "bare newline test", + PSC_STATE_FLAG_BARLF_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_BARLF] = event_time() + var_psc_barlf_ttl; + } + if ((state->flags & PSC_STATE_MASK_NSMTP_TODO_PASS_FAIL) + == PSC_STATE_FLAG_NSMTP_TODO) { + PSC_PASS_SESSION_STATE(state, "non-smtp test", + PSC_STATE_FLAG_NSMTP_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_NSMTP] = event_time() + var_psc_nsmtp_ttl; + } + if ((state->flags & PSC_STATE_MASK_PIPEL_TODO_PASS_FAIL) + == PSC_STATE_FLAG_PIPEL_TODO) { + PSC_PASS_SESSION_STATE(state, "pipelining test", + PSC_STATE_FLAG_PIPEL_PASS); + /* XXX Reset to PSC_TIME_STAMP_DISABLED on failure. */ + expire_time[PSC_TINDX_PIPEL] = event_time() + var_psc_pipel_ttl; } - /* Finally, execute the command. */ - if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0) { - write_stat = PSC_SEND_REPLY(state, + } + /* Command COUNT limit test. */ + if (++state->command_count > var_psc_cmd_count + && cmdp->action != psc_quit_cmd) { + msg_info("COMMAND COUNT LIMIT from [%s]:%s after %s", + PSC_CLIENT_ADDR_PORT(state), saved_where); + PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, + psc_smtpd_421_reply); + return; + } + /* Finally, execute the command. */ + if (cmdp->name == 0 || (cmdp->flags & PSC_SMTPD_CMD_FLAG_ENABLE) == 0) { + write_stat = PSC_SEND_REPLY(state, "502 5.5.2 Error: command not recognized\r\n"); - } else if (var_psc_enforce_tls - && (state->flags & PSC_STATE_FLAG_USING_TLS) == 0 - && (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0) { - write_stat = PSC_SEND_REPLY(state, + } else if (var_psc_enforce_tls + && (state->flags & PSC_STATE_FLAG_USING_TLS) == 0 + && (cmdp->flags & PSC_SMTPD_CMD_FLAG_PRE_TLS) == 0) { + write_stat = PSC_SEND_REPLY(state, "530 5.7.0 Must issue a STARTTLS command first\r\n"); - } else { - write_stat = cmdp->action(state, cmd_buffer_ptr); - if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY) - return; - } + } else { + write_stat = cmdp->action(state, cmd_buffer_ptr); + if (cmdp->flags & PSC_SMTPD_CMD_FLAG_DESTROY) + return; } /*