Package: release.debian.org Severity: normal Tags: trixie X-Debbugs-Cc: [email protected] Control: affects -1 + src:postfix User: [email protected] Usertags: pu
[ Reason ] There's a new upstream stable/bugfix/security release, including a fixes for a few security issues too. [ Tests ] It works in our environment for quite some time already. [ Risks ] As it is usual with postfix releases, it's safe to perform the update. All the changes are small, easy to understand and verify. I see no potential for any breakage here. [ Checklist ] [X] *all* changes are documented in the d/changelog [X] I reviewed all changes and I approve them [X] attach debdiff against the package in (old)stable [X] the issue is verified as fixed in unstable [ Changes ] The changelog is in the debdiff (d/changelog). Additionally, there's the upstream announcement, https://www.postfix.org/announcements/postfix-3.11.7.html The debdiff is below. Thanks, /mjt diff -Nru postfix-3.10.13/HISTORY postfix-3.10.14/HISTORY --- postfix-3.10.13/HISTORY 2026-08-09 23:47:07.000000000 +0300 +++ postfix-3.10.14/HISTORY 2026-09-06 17:10:47.000000000 +0300 @@ -29766,3 +29766,103 @@ Queue hygiene: the postdrop command accepted the null record type which the rest of Postfix ingnores. Reported by OpenAI Security. File: postdrop.c. + +20260903 + + Bug (defect introduced: Postfix 2.3, date: 20050323): the + SMTP client enhanced status code parser could process stale + data. Reported by Qualys, assisted by Claude Mythos Preview. + File: smtp_chat.c. + + Bug (defect introduced: Postfix 3.0, date: 20141117): don't + free() text before logging a fatal error message. Reported + by Qualys, assisted by Claude Mythos Preview. File: + postqueue.c. + + Bug (defect introduced: Postfix-beta, date: 19990119): the + pipe(8) delivery agent deleted a command-line argument if + the argument contained $user AND $user expanded to an empty + string, breaking the positional order of arguments. This + was a workaround for a problem that hopefully no longer + exists. Reported by Qualys, assisted by Claude Mythos + Preview. File: pipe/pipe.c. + + Code hygiene: in the SMTP client protocol engine, evaluate + the RETURN() macro argument before freeing resources. + Reported by Qualys, assisted by Claude Mythos Preview. File: + smtp_proto.c. + + Code hygiene: myrealloc(ptr, 0) still resulted in a panic. + Reported by Qualys, assisted by Claude Mythos Preview. Also + adopt a mystrndup() fix from Postfix 3.12. File: mymalloc.c. + + The postmap and postalias commands now log a warning when + creating a root-owned database file in a directory that is + not owned by root. They log that the database source file, + indexed file(s), and parent directory should have the same + owner, to prevent a privilege-escalation attack. Problem + reported by OpenAI Security, remediation strategy (don't + break PROD) by Wietse. Files: postalias.c, postmap.c. + + Hardening command-line email submission: the postdrop + command now disallows null and line-break characters in + queue file envelope records (line-break characters in other + queue file records are already neutralized by default with + "cleanup_replace_stray_cr_lf = yes"). + + The new constraint not only eliminates line-break injection + into local mailbox files as reported by OpenAI Security, + but also prevents other forms of misuse. Later, this + constraint may be moved into the Postfix core. Fix by Wietse. + File: postdrop.c. + + Shut up nagging from multiple AIs and harden the virtual + delivery agent against an evil SQL database. File: + virtual/mailbox.c. + + Bug (defect introduced: Postfix 3.4, date: 20180303): the + MySQL client setting "tls_verify_cert = yes" had no effect + with Oracle MySQL 8 and later. Report and fix by OpenAI + Security. File: dict_mysql.c. + + Isolation: stamp Postfix SMTP server TLS session tickets + with their master.cf service name. With this, an SMTP server + defined in master.cf will no longer accept tickets issued + by a different SMTP server defined in the same master.cf + file. Fix by OpenAI security. File: tls_server.c. + + Bug (introduced: Postfix 2.3, date: 20060629): a malicious + Milter or attacker-in-the-middle could trigger a null-terminated + heap memory overread in the SMTP daemon while formatting a + malformed multiline response. Fix from OpenAI Security + adopted with minor changes. Files: milter8.c, smtpd_chat.c. + + Bug (introduced: Postfix 3.9, date: 20240106) SMTP smuggling + was still possible with smtpd_proxy_filter (disabled by + default) when the after-filter SMTP server used the default + policy settings "smtpd_forbid_bare_newline_exclusions = + $mynetworks" and "smtpd_forbid_bare_newline = normalize". + Reported by OpenAI Security. Fix by Wietse. Files: + smtpd_proxy.[hc]. + + Cleanup: as suggested by OpenAI Security, eliminate stray + CR characters from the smtpd_proxy_filter input stream. + The before-proxy-filter SMTP server already eliminated stray + LF. File: smtpd/smtpd_proxy.[hc]. + + Portability: OpenBSD does not define NS_INT16SZ. Brad Smith. + File: dns.h. + + Shut up nagging about allowing gid==0 in database responses. + File: virtual/mailbox.c. + + Bug (defect introduced: Postfix 3.0, date: 20140707): null + pointer read error after receiving MAIL FROM, RCPT TO, and + VRFY with an UTF8 address but no SMTPUTF8 parameter. This + requires "smtputf8_enable = yes" (the default) and + "strict_smtputf8 = yes" (not default). With this, the SMTP + server did an unnecessary MAIL FROM reset without RCPT TO + reset. A crafted remote SMTP client could then send a DATA + command and crash the Postfix SMTP server with a null pointer + read error. Reported by Wonyoung Jung (정원영). File: + smtpd/smtpd.c. diff -Nru postfix-3.10.13/debian/changelog postfix-3.10.14/debian/changelog --- postfix-3.10.13/debian/changelog 2026-08-10 19:35:04.000000000 +0300 +++ postfix-3.10.14/debian/changelog 2026-09-08 15:31:38.000000000 +0300 @@ -1,3 +1,100 @@ +postfix (3.10.14-0+deb13u1) trixie; urgency=medium + + * new upstream stable/bugfix/security release + From the release announcement by Wietse Wenema at + https://www.postfix.org/announcements/postfix-3.11.7.html : + + These defects were found by "Qualys assisted by Claude Mythos Preview", + and by "OpenAI Security"; three date from 20 or more years ago. + + o SMTP smuggling: + + - Bug (introduced: Postfix 3.9, date: 20240106) SMTP smuggling was + still possible with smtpd_proxy_filter (disabled by default) when + the after-filter SMTP server used the default policy settings + "smtpd_forbid_bare_newline_exclusions = $mynetworks" and + "smtpd_forbid_bare_newline = normalize". + + - Bug (introduced: Postfix 3.11, date: 20250917): SMTP smuggling was + possible with smtpd_proxy_filter (disabled by default) when the + before-filter SMTP server added a "Require-TLS-ESMTP: yes" message + header, due to implementation edge cases. Adding this header is + enabled with the "requiretls_esmtp_header = yes" default setting. + + o Server crashes and panic()s: + + - Bug (defect introduced: Postfix 3.0, date: 20140707): null pointer read + error after receiving MAIL FROM, RCPT TO, and VRFY with an UTF8 address + but no SMTPUTF8 parameter. This requires "smtputf8_enable = yes" (the + default) and "strict_smtputf8 = yes" (not default). With this, the SMTP + server did an unnecessary MAIL FROM reset without RCPT TO reset. + A crafted remote SMTP client could then send a DATA command and crash a + Postfix SMTP server process with a null pointer read error. + + o Other bugs: + + - Bug (defect introduced: Postfix 3.4, date: 20180303): the MySQL + client setting "tls_verify_cert = yes" had no effect with Oracle + MySQL 8 and later. + + - Bug (defect introduced: Postfix-beta, date: 19990119): the pipe(8) + delivery agent deleted a command-line argument if the argument contained + $user AND $user expanded to an empty string, breaking the positional + order of arguments. This was a workaround for a problem that hopefully + no longer exists. + + - Bug (defect introduced: Postfix 2.3, date: 20050323): the SMTP client + enhanced status code parser could process stale data when a remote SMTP + server sent a three-digit reply without other text. + + o TLS: + + - Isolation: stamp Postfix SMTP server TLS session tickets with their + master.cf service name. With this, an SMTP server defined in master.cf + will no longer accept tickets issued by a different SMTP server defined + in the same master.cf file. + + o Configuration safety: + + - The postmap and postalias commands now log a warning when creating a + root-owned database file in a directory that is not owned by root. They + log that the database source file, indexed file(s), and parent directory + should have the same owner, to prevent a privilege-escalation attack. + + o Read after free, memory over-read: + + - Bug (introduced: Postfix 2.3, date: 20060629): a malicious Milter or + attacker-in-the-middle could trigger a null-terminated heap memory + overread in the SMTP daemon while formatting a malformed multiline + response. + + - Bug (defect introduced: Postfix 3.0, date: 20141117): in the postqueue + command don't free() text before logging a fatal error message. + + - Code hygiene: in the SMTP client protocol engine, evaluate a RETURN() + macro argument before freeing resources. + + o Code hardening (defense in depth, prevention): + + - Hardening command-line email submission: the postdrop command now + disallows null and line-break characters in queue file envelope records + (line-break characters in non-envelope queue file records are already + neutralized by default with "cleanup_replace_stray_cr_lf = yes"). + + - The new constraint not only eliminates line-break injection into local + mailbox files, but also prevents other forms of misuse. Later, this + constraint may be moved into the Postfix core. + + - Shut up nagging from multiple AIs and harden the virtual delivery agent + against an evil (LDAP or SQL) database. + + - Code hygiene: myrealloc(ptr, 0) still resulted in a panic. + Also adopt a mystrndup() fix from Postfix 3.12. + + * debian-defaults.patch: fix typo (inet_protocools) (Closes: #1144900) + + -- Michael Tokarev <[email protected]> Tue, 08 Sep 2026 15:31:38 +0300 + postfix (3.10.13-0+deb13u1) trixie-security; urgency=medium * new upstream stable/bugfix/security release diff -Nru postfix-3.10.13/debian/patches/debian-defaults.patch postfix-3.10.14/debian/patches/debian-defaults.patch --- postfix-3.10.13/debian/patches/debian-defaults.patch 2026-08-10 19:08:30.000000000 +0300 +++ postfix-3.10.14/debian/patches/debian-defaults.patch 2026-09-08 15:11:57.000000000 +0300 @@ -41,7 +41,7 @@ +# separated by whitespace or commas. The form "all" is equivalent +# to "ipv4, ipv6". +# -+#inet_protocools = ipv4 ++#inet_protocols = ipv4 +inet_protocols = all + # INTERNET HOST AND DOMAIN NAMES diff -Nru postfix-3.10.13/src/dns/dns.h postfix-3.10.14/src/dns/dns.h --- postfix-3.10.13/src/dns/dns.h 2024-11-21 20:49:05.000000000 +0300 +++ postfix-3.10.14/src/dns/dns.h 2026-09-04 00:12:22.000000000 +0300 @@ -54,6 +54,10 @@ #endif +#ifndef NS_INT16SZ +#define NS_INT16SZ 2 +#endif + /* * Provide API compatibility for systems without res_nxxx() API. Also * require calling dns_get_h_errno() instead of directly accessing the diff -Nru postfix-3.10.13/src/global/dict_mysql.c postfix-3.10.14/src/global/dict_mysql.c --- postfix-3.10.13/src/global/dict_mysql.c 2025-02-07 18:36:50.000000000 +0300 +++ postfix-3.10.14/src/global/dict_mysql.c 2026-09-04 00:12:22.000000000 +0300 @@ -118,7 +118,8 @@ #if defined(MARIADB_BASE_VERSION) && MYSQL_VERSION_ID >= 50023 #define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_VERIFY_SERVER_CERT #elif MYSQL_VERSION_ID >= 80000 -#define DICT_MYSQL_SSL_VERIFY_SERVER_CERT MYSQL_OPT_SSL_MODE +/* 202607 OpenAI: MYSQL_OPT_SSL_MODE is an enum, not a bool. */ +#define DICT_MYSQL_SSL_MODE MYSQL_OPT_SSL_MODE #endif /* @@ -171,7 +172,7 @@ char *tls_CAfile; char *tls_CApath; char *tls_ciphers; -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) || defined(DICT_MYSQL_SSL_MODE) int tls_verify_cert; #endif int require_result_set; @@ -601,6 +602,11 @@ */ static void plmysql_connect_single(DICT_MYSQL *dict_mysql, HOST *host) { +#if defined(DICT_MYSQL_SSL_MODE) + enum mysql_ssl_mode ssl_mode; + +#endif + if ((host->db = mysql_init(NULL)) == NULL) msg_fatal("dict_mysql: insufficient memory"); if (dict_mysql->option_file) @@ -628,7 +634,19 @@ dict_mysql->tls_CAfile, dict_mysql->tls_CApath, dict_mysql->tls_ciphers); #endif -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_MODE) + if (dict_mysql->tls_verify_cert != -1) { + /* MYSQL_OPT_SSL_MODE enum values are not Boolean values. */ + ssl_mode = dict_mysql->tls_verify_cert ? SSL_MODE_VERIFY_IDENTITY : + SSL_MODE_PREFERRED; + if (mysql_options(host->db, DICT_MYSQL_SSL_MODE, &ssl_mode) != 0) { + msg_warn("dict_mysql: could not set TLS mode for host %s: %s", + host->hostname, mysql_error(host->db)); + plmysql_down_host(host, dict_mysql->retry_interval); + return; + } + } +#elif defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) if (dict_mysql->tls_verify_cert != -1) mysql_options(host->db, DICT_MYSQL_SSL_VERIFY_SERVER_CERT, &dict_mysql->tls_verify_cert); @@ -704,7 +722,7 @@ dict_mysql->tls_CAfile = cfg_get_str(p, "tls_CAfile", NULL, 0, 0); dict_mysql->tls_CApath = cfg_get_str(p, "tls_CApath", NULL, 0, 0); dict_mysql->tls_ciphers = cfg_get_str(p, "tls_ciphers", NULL, 0, 0); -#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) +#if defined(DICT_MYSQL_SSL_VERIFY_SERVER_CERT) || defined(DICT_MYSQL_SSL_MODE) dict_mysql->tls_verify_cert = cfg_get_bool(p, "tls_verify_cert", -1); #endif dict_mysql->require_result_set = cfg_get_bool(p, "require_result_set", 1); diff -Nru postfix-3.10.13/src/global/mail_version.h postfix-3.10.14/src/global/mail_version.h --- postfix-3.10.13/src/global/mail_version.h 2026-08-09 23:47:52.000000000 +0300 +++ postfix-3.10.14/src/global/mail_version.h 2026-09-07 19:24:23.000000000 +0300 @@ -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 "20260809" -#define MAIL_VERSION_NUMBER "3.10.13" +#define MAIL_RELEASE_DATE "20260907" +#define MAIL_VERSION_NUMBER "3.10.14" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff -Nru postfix-3.10.13/src/milter/milter8.c postfix-3.10.14/src/milter/milter8.c --- postfix-3.10.13/src/milter/milter8.c 2026-07-06 02:29:02.000000000 +0300 +++ postfix-3.10.14/src/milter/milter8.c 2026-09-04 00:12:22.000000000 +0300 @@ -955,6 +955,64 @@ return (err); } +/* milter8_valid_smtp_reply - validate SMTP reply (after 202607 OpenAI) */ + +static int milter8_valid_smtp_reply(VSTRING *buf) +{ + const unsigned char *cp = (unsigned char *) STR(buf); + const unsigned char *buf_end = cp + LEN(buf); + + /* Exclude one terminal Milter protocol null from the reply text. */ + if (buf_end > cp && buf_end[-1] == 0) + buf_end -= 1; + + if (memchr(cp, 0, buf_end - cp) != 0) + return (0); + + while (cp < buf_end) { + const unsigned char *line_end; + ssize_t line_len; + int final; + + /* Part of a breaking change, not allowed in stable release. */ +#ifdef SNAPSHOT + unsigned char code[3]; + int first = 1; + +#endif + + /* A final line must not end in a line terminator. */ + line_len = strcspn((const char *) cp, "\r\n"); + line_end = cp + line_len; + final = (line_end == buf_end); + + /* Each line must match /^ddd(-| )d?/. */ + if (line_len < 4 || (cp[0] != '4' && cp[0] != '5') || !ISDIGIT(cp[1]) + || !ISDIGIT(cp[2]) || (cp[3] != (final ? ' ' : '-')) + || (line_len > 4 && ISDIGIT(cp[4]) && cp[4] != cp[0])) + return (0); + + /* Part of a breaking change, not allowed in stable release. */ +#ifdef SNAPSHOT + if (first) { + code[0] = cp[0]; + code[1] = cp[1]; + code[2] = cp[2]; + first = 0; + } else if (cp[0] != code[0] || cp[1] != code[1] || cp[2] != code[2]) { + return (0); + } +#endif + /* A non-final line must end in a line terminator. */ + if (final) + return (1); + if (line_end > buf_end - 2 || *line_end != '\r' || line_end[1] != '\n') + return (0); + cp = line_end + 2; + } + return (0); +} + /* milter8_event - report event and receive reply */ static const char *milter8_event(MILTER8 *milter, int event, @@ -1290,13 +1348,7 @@ MILTER8_DATA_BUFFER, milter->buf, MILTER8_DATA_END) != 0) MILTER8_EVENT_BREAK(milter->def_reply); - /* XXX Enforce this for each line of a multi-line reply. */ - if ((STR(milter->buf)[0] != '4' && STR(milter->buf)[0] != '5') - || !ISDIGIT(STR(milter->buf)[1]) - || !ISDIGIT(STR(milter->buf)[2]) - || (STR(milter->buf)[3] != ' ' && STR(milter->buf)[3] != '-') - || (ISDIGIT(STR(milter->buf)[4]) - && (STR(milter->buf)[4] != STR(milter->buf)[0]))) { + if (!milter8_valid_smtp_reply(milter->buf)) { msg_warn("milter %s: malformed reply: %s", milter->m.name, STR(milter->buf)); milter8_conf_error(milter); diff -Nru postfix-3.10.13/src/pipe/pipe.c postfix-3.10.14/src/pipe/pipe.c --- postfix-3.10.13/src/pipe/pipe.c 2025-02-02 00:50:29.000000000 +0300 +++ postfix-3.10.14/src/pipe/pipe.c 2026-09-04 00:12:43.000000000 +0300 @@ -765,12 +765,6 @@ * Either anything to the left of the extension delimiter or, * in absence of the latter, anything to the left of the * rightmost @. - * - * Beware: if the user name is blank (e.g. +user@host), the - * argument is suppressed. This is necessary to allow for - * cyrus bulletin-board (global mailbox) delivery. XXX But, - * skipping empty user parts will also prevent other - * expansions of this specific command-line argument. */ if (state.expand_flag & PIPE_FLAG_USER) { morph_recipient(buf, rcpt_list->info[i].address, @@ -780,8 +774,7 @@ rcpt_list->info[i].address); if (*var_rcpt_delim) split_addr(STR(buf), var_rcpt_delim); - if (*STR(buf) == 0) - continue; + /* 202607 Qualys+Mythos: don't skip arg if $user is "".*/ dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf)); } diff -Nru postfix-3.10.13/src/postalias/postalias.c postfix-3.10.14/src/postalias/postalias.c --- postfix-3.10.13/src/postalias/postalias.c 2024-04-22 16:56:32.000000000 +0300 +++ postfix-3.10.14/src/postalias/postalias.c 2026-09-04 00:12:43.000000000 +0300 @@ -336,11 +336,30 @@ /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postalias does not - * allow privilege escalation. + * allow privilege escalation. If the source file is owned by root, but + * the parent directory is not, log a warning with suggestions. */ - if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0 - && (st.st_uid != geteuid() || st.st_gid != getegid())) - set_eugid(st.st_uid, st.st_gid); + if ((postalias_flags & POSTALIAS_FLAG_AS_OWNER) && getuid() == 0) { + if (st.st_uid != 0) { + set_eugid(st.st_uid, st.st_gid); + } else { + char *parent_dir; + struct stat parent_st; + + parent_dir = sane_dirname((VSTRING *) 0, path_name); + if (stat(parent_dir, &parent_st) < 0) + msg_fatal("stat %s: %m", parent_dir); + if (parent_st.st_uid != 0) { + msg_warn("Creating root-owned database file(s) %s.* " + "in non-root-owned directory %s. Files " + "%s, %s.*, and directory %s should have " + "the same owner. Please fix ownership " + "to avoid a privilege-escalation attack.", + path_name, parent_dir, VSTREAM_PATH(source_fp), + path_name, parent_dir); + } + } + } /* * Override the default per-table cache size for DB map (re)builds. We diff -Nru postfix-3.10.13/src/postdrop/postdrop.c postfix-3.10.14/src/postdrop/postdrop.c --- postfix-3.10.13/src/postdrop/postdrop.c 2026-08-09 23:47:07.000000000 +0300 +++ postfix-3.10.14/src/postdrop/postdrop.c 2026-09-04 00:12:43.000000000 +0300 @@ -514,6 +514,11 @@ msg_fatal("uid=%ld: malformed input", (long) uid); if (rec_type == 0 || strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); + /* 202607 OpenAI: reject line breaks and nulls in envelope content. */ + if (rec_type != REC_TYPE_NORM && rec_type != REC_TYPE_CONT + && strcspn(vstring_str(buf), "\r\n") != VSTRING_LEN(buf)) + msg_fatal("uid=%ld: null or line break in '%s' record type: %.200s", + (long) uid, rec_type_name(rec_type), vstring_str(buf)); if (rec_type == **expected) expected++; /* Override time information from the untrusted caller. */ diff -Nru postfix-3.10.13/src/postmap/postmap.c postfix-3.10.14/src/postmap/postmap.c --- postfix-3.10.13/src/postmap/postmap.c 2023-10-12 18:34:40.000000000 +0300 +++ postfix-3.10.14/src/postmap/postmap.c 2026-09-04 00:12:43.000000000 +0300 @@ -429,11 +429,30 @@ /* * If running as root, run as the owner of the source file, so that the * result shows proper ownership, and so that a bug in postmap does not - * allow privilege escalation. + * allow privilege escalation. If the source file is owned by root, but + * the parent directory is not, log a warning with suggestions. */ - if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0 - && (st.st_uid != geteuid() || st.st_gid != getegid())) - set_eugid(st.st_uid, st.st_gid); + if ((postmap_flags & POSTMAP_FLAG_AS_OWNER) && getuid() == 0) { + if (st.st_uid != 0) { + set_eugid(st.st_uid, st.st_gid); + } else { + char *parent_dir; + struct stat parent_st; + + parent_dir = sane_dirname((VSTRING *) 0, path_name); + if (stat(parent_dir, &parent_st) < 0) + msg_fatal("stat %s: %m", parent_dir); + if (parent_st.st_uid != 0) { + msg_warn("Creating root-owned database file(s) %s.* " + "in non-root-owned directory %s. Files " + "%s, %s.*, and directory %s should have " + "the same owner. Please fix ownership " + "to avoid a privilege-escalation attack.", + path_name, parent_dir, VSTREAM_PATH(source_fp), + path_name, parent_dir); + } + } + } /* * Override the default per-table cache size for DB map (re)builds. We diff -Nru postfix-3.10.13/src/postqueue/postqueue.c postfix-3.10.14/src/postqueue/postqueue.c --- postfix-3.10.13/src/postqueue/postqueue.c 2023-12-04 03:11:58.000000000 +0300 +++ postfix-3.10.14/src/postqueue/postqueue.c 2026-09-04 00:12:43.000000000 +0300 @@ -422,10 +422,11 @@ stat = vstream_pclose(showq); } argv_free(argv); - myfree(showq_path); + /* 202606 Qualys+Mythos: don't free() text before logging. */ if (stat != 0) msg_fatal_status(stat < 0 ? EX_OSERR : EX_SOFTWARE, "Error running %s", showq_path); + myfree(showq_path); } /* diff -Nru postfix-3.10.13/src/smtp/smtp_chat.c postfix-3.10.14/src/smtp/smtp_chat.c --- postfix-3.10.13/src/smtp/smtp_chat.c 2021-10-02 17:46:46.000000000 +0300 +++ postfix-3.10.14/src/smtp/smtp_chat.c 2026-09-04 00:12:43.000000000 +0300 @@ -413,7 +413,8 @@ if (three_digs != 0) { rdata.code = atoi(STR(session->buffer)); if (strchr("245", STR(session->buffer)[0]) != 0) { - for (cp = STR(session->buffer) + 4; *cp == ' '; cp++) + /* 202606 Qualys+Mythos: start loop immediately after 'ddd'. */ + for (cp = STR(session->buffer) + 3; *cp == ' '; cp++) /* void */ ; if ((len = dsn_valid(cp)) > 0 && *cp == *STR(session->buffer)) { vstring_strncpy(rdata.dsn_buf, cp, len); diff -Nru postfix-3.10.13/src/smtp/smtp_proto.c postfix-3.10.14/src/smtp/smtp_proto.c --- postfix-3.10.13/src/smtp/smtp_proto.c 2026-06-17 20:06:59.000000000 +0300 +++ postfix-3.10.14/src/smtp/smtp_proto.c 2026-09-04 00:12:43.000000000 +0300 @@ -1573,7 +1573,10 @@ /* Caution: changes to RETURN() also affect code outside the main loop. */ + /* 202607 Qualys+Mythos: evaluate argument before freeing resources. */ + #define RETURN(x) do { \ + int _rv = (x); \ if (recv_state != SMTP_STATE_LAST) \ DONT_CACHE_THIS_SESSION; \ vstring_free(next_command); \ @@ -1581,7 +1584,7 @@ myfree((void *) survivors); \ if (session->mime_state) \ session->mime_state = mime_state_free(session->mime_state); \ - return (x); \ + return (_rv); \ } while (0) #define SENDER_IS_AHEAD \ diff -Nru postfix-3.10.13/src/smtpd/smtpd.c postfix-3.10.14/src/smtpd/smtpd.c --- postfix-3.10.13/src/smtpd/smtpd.c 2026-08-09 23:47:07.000000000 +0300 +++ postfix-3.10.14/src/smtpd/smtpd.c 2026-09-04 00:12:43.000000000 +0300 @@ -4256,8 +4256,8 @@ state->act_size += len + 2; if (*start == '.' && proxy != 0 && state->bdat_prev_rec_type != REC_TYPE_CONT) - if (out_record(out_stream, REC_TYPE_CONT, ".", 1) < 0) - state->err = out_error; + /* 202507 OpenAI: more robust dot-stuffing. */ + vstring_prepend(state->bdat_get_buffer, ".", 1); if (state->err == CLEANUP_STAT_OK && out_record(out_stream, curr_rec_type, vstring_str(state->bdat_get_buffer), @@ -4441,7 +4441,7 @@ /* Fix 20140707: Check the VRFY command. */ if (smtputf8 == 0 && var_strict_smtputf8) { if (*STR(state->addr_buf) && !allascii(STR(state->addr_buf))) { - mail_reset(state); + /* Wonyoung.Jung 20260811: don't reset SMTP state. */ smtpd_chat_reply(state, "553 5.6.7 Must declare SMTPUTF8 to send unicode address"); return (-1); } diff -Nru postfix-3.10.13/src/smtpd/smtpd_chat.c postfix-3.10.14/src/smtpd/smtpd_chat.c --- postfix-3.10.13/src/smtpd/smtpd_chat.c 2021-10-02 17:46:46.000000000 +0300 +++ postfix-3.10.14/src/smtpd/smtpd_chat.c 2026-09-04 00:12:43.000000000 +0300 @@ -207,6 +207,7 @@ char *next; char *end; const char *footer; + ssize_t line_len; /* 202607 OpenAI line length checks */ /* * Slow down clients that make errors. Sleep-on-anything slows down @@ -226,20 +227,26 @@ /* All 5xx replies must have a 5.xx.xx detail code. */ for (cp = STR(state->buffer), end = cp + strlen(STR(state->buffer));;) { + if ((next = strstr(cp, "\r\n")) != 0) + line_len = next - cp; + else + line_len = end - cp; if (var_soft_bounce) { - if (cp[0] == '5') { + if (line_len > 0 && cp[0] == '5') { cp[0] = '4'; - if (cp[4] == '5') + if (line_len > 4 && cp[4] == '5') cp[4] = '4'; } } /* This is why we use strlen() above instead of VSTRING_LEN(). */ - if ((next = strstr(cp, "\r\n")) != 0) { + if (next != 0) { *next = 0; - if (next[2] != 0) - cp[3] = '-'; /* contact footer kludge */ - else + if (next[2] != 0) { + if (line_len > 3) + cp[3] = '-'; /* contact footer kludge */ + } else { next = end; /* strip trailing \r\n */ + } } else { next = end; } @@ -248,7 +255,7 @@ if (msg_verbose) msg_info("> %s: %s", state->namaddr, cp); - smtp_fputs(cp, next - cp, state->client); + smtp_fputs(cp, line_len, state->client); if (next < end) cp = next + 2; else diff -Nru postfix-3.10.13/src/smtpd/smtpd_proxy.c postfix-3.10.14/src/smtpd/smtpd_proxy.c --- postfix-3.10.13/src/smtpd/smtpd_proxy.c 2022-11-28 23:07:21.000000000 +0300 +++ postfix-3.10.14/src/smtpd/smtpd_proxy.c 2026-09-04 16:45:16.000000000 +0300 @@ -849,12 +849,39 @@ return (rec_type); } +/* smtpd_proxy_fix_line_breaks - replace line-break crud with space */ + +static const char *smtpd_proxy_fix_line_breaks(SMTPD_PROXY *proxy, + const char *data, + ssize_t len) +{ + char *cp; + + /* + * We still need to eliminate <CR>. + * + * <LF> is already eliminated by smtp_get_no_except() (used for BDAT), and + * by smtp_get() (used for DATA) which calls smtp_get_no_except(). + */ + if (memchr(data, '\r', len) != 0) { + if (proxy->data_buf == 0) + proxy->data_buf = vstring_alloc(100); + vstring_memcpy(proxy->data_buf, data, len); + for (cp = STR(proxy->data_buf); cp < vstring_end(proxy->data_buf); cp++) + if (*cp == '\r') + *cp = ' '; + data = STR(proxy->data_buf); + } + return (data); +} + /* smtpd_proxy_rec_put - send message content, rec_put() clone */ static int smtpd_proxy_rec_put(VSTREAM *stream, int rec_type, const char *data, ssize_t len) { const char *myname = "smtpd_proxy_rec_put"; + SMTPD_PROXY *proxy = VSTREAM_TO_SMTPD_STATE(stream)->proxy; int err = 0; /* @@ -865,16 +892,29 @@ (void) smtpd_proxy_rdwr_error(VSTREAM_TO_SMTPD_STATE(stream), err); return (REC_TYPE_ERROR); } + if (rec_type != REC_TYPE_CONT && rec_type != REC_TYPE_NORM) + msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + + /* 202608 OpenAI: fixed guard against header prepend edge cases. */ + if (proxy->last_text_rec != REC_TYPE_CONT) { + /* Wietse: leading '.' may be followed by line-break crud. */ + if (data[0] == '.' && (len == 1 || data[1] != '.')) { + msg_warn("prepending '.' to malformed line: '%.*s'", + len > 10 ? 10 : (int) len, data); + smtp_fwrite(".", 1, stream); + } + } /* * Send one content record. Errors and results must be as with rec_put(). */ + /* 202607 OpenAI: replace line-break crud with space. */ + data = smtpd_proxy_fix_line_breaks(proxy, data, len); if (rec_type == REC_TYPE_NORM) smtp_fputs(data, len, stream); else if (rec_type == REC_TYPE_CONT) smtp_fwrite(data, len, stream); - else - msg_panic("%s: need REC_TYPE_NORM or REC_TYPE_CONT", myname); + proxy->last_text_rec = rec_type; return (rec_type); } @@ -1014,10 +1054,11 @@ * When an operation has many arguments it is safer to use named * parameters, and have the compiler enforce the argument count. */ -#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12) \ +#define SMTPD_PROXY_ALLOC(p, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, \ + a12, a13, a14) \ ((p) = (SMTPD_PROXY *) mymalloc(sizeof(*(p))), (p)->a1, (p)->a2, \ (p)->a3, (p)->a4, (p)->a5, (p)->a6, (p)->a7, (p)->a8, (p)->a9, \ - (p)->a10, (p)->a11, (p)->a12, (p)) + (p)->a10, (p)->a11, (p)->a12, (p)->a13, (p)->a14, (p)) /* * Sanity check. @@ -1037,7 +1078,9 @@ rec_put = smtpd_proxy_rec_put, flags = flags, service_stream = 0, service_name = service, timeout = timeout, - ehlo_name = ehlo_name, mail_from = mail_from); + ehlo_name = ehlo_name, mail_from = mail_from, + last_text_rec = 0, + data_buf = 0); if (smtpd_proxy_connect(state) < 0) { /* NOT: smtpd_proxy_free(state); we still need proxy->reply. */ return (-1); @@ -1067,7 +1110,9 @@ rec_put = smtpd_proxy_save_rec_put, flags = flags, service_stream = 0, service_name = service, timeout = timeout, - ehlo_name = ehlo_name, mail_from = mail_from); + ehlo_name = ehlo_name, mail_from = mail_from, + last_text_rec = 0, + data_buf = 0); return (0); #endif } @@ -1110,6 +1155,8 @@ vstring_free(proxy->request); if (proxy->reply != 0) vstring_free(proxy->reply); + if (proxy->data_buf != 0) + vstring_free(proxy->data_buf); myfree((void *) proxy); state->proxy = 0; diff -Nru postfix-3.10.13/src/smtpd/smtpd_proxy.h postfix-3.10.14/src/smtpd/smtpd_proxy.h --- postfix-3.10.13/src/smtpd/smtpd_proxy.h 2013-11-18 20:00:36.000000000 +0400 +++ postfix-3.10.14/src/smtpd/smtpd_proxy.h 2026-09-04 16:09:50.000000000 +0300 @@ -37,6 +37,8 @@ int timeout; const char *ehlo_name; const char *mail_from; + int last_text_rec; + VSTRING *data_buf; } SMTPD_PROXY; #define SMTPD_PROXY_FLAG_SPEED_ADJUST (1<<0) diff -Nru postfix-3.10.13/src/tls/tls_server.c postfix-3.10.14/src/tls/tls_server.c --- postfix-3.10.13/src/tls/tls_server.c 2024-10-11 01:15:25.000000000 +0300 +++ postfix-3.10.14/src/tls/tls_server.c 2026-09-04 00:12:43.000000000 +0300 @@ -827,6 +827,11 @@ const char *cipher_list; TLS_APPL_STATE *app_ctx = props->ctx; int log_mask = app_ctx->log_mask; + /* 202607 OpenAI: isolate session tickets by master.cf service name. */ + EVP_MD_CTX *sessid_ctx_md = 0; + unsigned char sessid_ctx[EVP_MAX_MD_SIZE]; + unsigned int sessid_ctx_len; + static const char sessid_ctx_label[] = "Postfix/TLS serverid"; /* * Implicitly enable logging of trust chain errors when verified certs @@ -868,6 +873,29 @@ TLScontext->stream = props->stream; TLScontext->mdalg = props->mdalg; + /* + * The serverid already partitions stateful sessions in the tlsmgr + * session cache. Apply the same partition to stateless sessions; all + * services in one Postfix instance share the tlsmgr ticket keys. + */ + if (tls_digest_byname(LN_sha256, &sessid_ctx_md) == 0 + || EVP_DigestUpdate(sessid_ctx_md, sessid_ctx_label, + sizeof(sessid_ctx_label)) != 1 + || EVP_DigestUpdate(sessid_ctx_md, props->serverid, + strlen(props->serverid) + 1) != 1 + || EVP_DigestFinal_ex(sessid_ctx_md, sessid_ctx, &sessid_ctx_len) != 1 + || sessid_ctx_len > SSL_MAX_SID_CTX_LENGTH + || SSL_set_session_id_context(TLScontext->con, sessid_ctx, + sessid_ctx_len) != 1) { + msg_warn("Could not set TLS session ID context"); + tls_print_errors(); + if (sessid_ctx_md) + EVP_MD_CTX_destroy(sessid_ctx_md); + tls_free_context(TLScontext); + return (0); + } + EVP_MD_CTX_destroy(sessid_ctx_md); + if (!SSL_set_ex_data(TLScontext->con, TLScontext_index, TLScontext)) { msg_warn("Could not set application data for 'TLScontext->con'"); tls_print_errors(); diff -Nru postfix-3.10.13/src/util/mymalloc.c postfix-3.10.14/src/util/mymalloc.c --- postfix-3.10.13/src/util/mymalloc.c 2026-06-28 23:39:06.000000000 +0300 +++ postfix-3.10.14/src/util/mymalloc.c 2026-09-04 00:12:43.000000000 +0300 @@ -197,6 +197,10 @@ #ifndef NO_SHARED_EMPTY_STRINGS if (ptr == empty_string) return (mymalloc(len)); + if (len == 0) { + myfree(ptr); + return (mymalloc(0)); + } #endif /* @@ -204,7 +208,7 @@ * allows us to catch integer overflow problems that weren't already * caught up-stream. */ - if (len < 1) + if (len < 0) msg_panic("myrealloc: requested length %ld", (long) len); #ifdef MYMALLOC_FUZZ len += MYMALLOC_FUZZ; @@ -266,7 +270,7 @@ if (len < 0) msg_panic("mystrndup: requested length %ld", (long) len); #ifndef NO_SHARED_EMPTY_STRINGS - if (*str == 0) + if (*str == 0 || /* fix 20220615 */ len == 0) return ((char *) empty_string); #endif if ((cp = memchr(str, 0, len)) != 0) diff -Nru postfix-3.10.13/src/virtual/Makefile.in postfix-3.10.14/src/virtual/Makefile.in --- postfix-3.10.13/src/virtual/Makefile.in 2024-03-10 00:34:49.000000000 +0300 +++ postfix-3.10.14/src/virtual/Makefile.in 2026-09-04 00:12:43.000000000 +0300 @@ -95,6 +95,7 @@ mailbox.o: ../../include/nvtable.h mailbox.o: ../../include/recipient_list.h mailbox.o: ../../include/safe_open.h +mailbox.o: ../../include/sane_strtol.h mailbox.o: ../../include/sent.h mailbox.o: ../../include/set_eugid.h mailbox.o: ../../include/stringops.h diff -Nru postfix-3.10.13/src/virtual/mailbox.c postfix-3.10.14/src/virtual/mailbox.c --- postfix-3.10.13/src/virtual/mailbox.c 2018-01-06 18:43:44.000000000 +0300 +++ postfix-3.10.14/src/virtual/mailbox.c 2026-09-04 00:12:43.000000000 +0300 @@ -57,6 +57,7 @@ #include <mymalloc.h> #include <stringops.h> #include <set_eugid.h> +#include <sane_strtol.h> /* Global library. */ @@ -176,7 +177,8 @@ const char *uid_res; const char *gid_res; DSN_BUF *why = state.msg_attr.why; - long n; + char *end; + unsigned long n; /* * Make verbose logging easier to understand. @@ -215,6 +217,15 @@ #define RETURN(res) { myfree(usr_attr.mailbox); return (res); } + if (strstr(usr_attr.mailbox + strlen(var_virt_mailbox_base), "/../")) { + msg_warn("recipient %s: bad mailbox path %s in %s", + state.msg_attr.user, mailbox_res, virtual_mailbox_maps->title); + dsb_simple(why, "4.3.5", "mail system configuration error"); + *statusp = defer_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr)); + RETURN(YES); + } + /* * Look up the mailbox owner rights. Defer in case of trouble. */ @@ -228,7 +239,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - if ((n = atol(uid_res)) < var_virt_minimum_uid) { + usr_attr.uid = (uid_t) (n = sane_strtoul(uid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.uid != n + || usr_attr.uid == (uid_t) - 1 /* Special for safe_open()) */ + || usr_attr.uid < var_virt_minimum_uid) { msg_warn("recipient %s: bad uid %s in %s", state.msg_attr.user, uid_res, virtual_uid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -236,7 +250,6 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - usr_attr.uid = (uid_t) n; /* * Look up the mailbox group rights. Defer in case of trouble. @@ -251,7 +264,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - if ((n = atol(gid_res)) <= 0) { + usr_attr.gid = (gid_t) (n = sane_strtoul(gid_res, &end, 10)); + if (*end != 0 || errno != 0 || usr_attr.gid !=n + || usr_attr.gid == 0 /* Backwards compatibility */ + || usr_attr.gid == (gid_t) - 1) { /* Special for safe_open()) */ msg_warn("recipient %s: bad gid %s in %s", state.msg_attr.user, gid_res, virtual_gid_maps->title); dsb_simple(why, "4.3.5", "mail system configuration error"); @@ -259,12 +275,10 @@ BOUNCE_ATTR(state.msg_attr)); RETURN(YES); } - usr_attr.gid = (gid_t) n; - if (msg_verbose) - msg_info("%s[%d]: set user_attr: %s, uid = %u, gid = %u", + msg_info("%s[%d]: set user_attr: %s, uid = %lu, gid = %lu", myname, state.level, usr_attr.mailbox, - (unsigned) usr_attr.uid, (unsigned) usr_attr.gid); + (unsigned long) usr_attr.uid, (unsigned long) usr_attr.gid); /* * Deliver to mailbox or to maildir.

