Prereq: "3.5.26" diff -ur --new-file /var/tmp/postfix-3.5.26/src/global/mail_version.h ./src/global/mail_version.h --- /var/tmp/postfix-3.5.26/src/global/mail_version.h 2026-07-06 12:05:03.000000000 -0400 +++ ./src/global/mail_version.h 2026-08-09 19:07:02.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 "20260706" -#define MAIL_VERSION_NUMBER "3.5.26" +#define MAIL_RELEASE_DATE "20260809" +#define MAIL_VERSION_NUMBER "3.5.27" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff -ur --new-file /var/tmp/postfix-3.5.26/HISTORY ./HISTORY --- /var/tmp/postfix-3.5.26/HISTORY 2026-07-06 12:04:09.000000000 -0400 +++ ./HISTORY 2026-08-09 19:06:27.000000000 -0400 @@ -25634,3 +25634,115 @@ with a pathname that was already wiped and free()d, but not yet reused. Reported by Qualys, assisted by Claude Mythos Preview. File: postdrop.c. + +20260806 + + Bug (defect introduced: Postfix 2.10, date: 20120617): + uninitialized memory read in postscreen HaProxy client after + remote I/O exception, causing garbage to be logged. Reported + by Qualys, assisted by Claude Mythos Preview. File: + postscreen_haproxy.c. + + Latent bug (defect introduced: Postfix 2.7, date: 20090618): + uninitialized memory read after dnsblog(8) returns a string + that is not an IPv4 address. Reported by Qualys, assisted + by Claude Mythos Preview. File: postscreen_dnsbl.c. + + Bug (defect introduced: Postfix 2.8, date: 20100914): + read-after-free in the PSC_CALL_BACK_NOTIFY() macro. This + had no effect on program execution, because myfree() wiped + memory, and that memory was not yet reused. Problem reported + by Qualys, assisted by Claude Mythos Preview. File: + postscreen_dnsbl.c. + + Bug (defect introduced: before Postfix alpha, date 19970424): + the DNS client could read up to two bytes past the end of + an MX record, before discovering that the record was too + short. Problem reported by Qualys, assisted by Claude Mythos + Preview. File: dns_lookup.c. + + Bug (defect introduced: before Postfix alpha, date: 19971106): + 'int' over-shift, in the queue file record-length parser. + Postfix programs do not generate such records, but an + attacker could cause postdrop to reject input or panic(). + Reported by Qualys, assisted by Claude Mythos Preview. File: + record.c. + + Bug (defect introduced: Postfix 2.2, date: 20050117): + non-transitive comparison of IPv4 addresses. Reported by + Qualys, assisted by Claude Mythos Preview. File: sock_addr.c. + + Read after free (no privilege escalation) in debug logging + (defect introduced: Postfix 2.2, date: 20050117). Reported + by Qualys, assisted by Claude Mythos Preview. File: + util/inet_connect.c. + + Bug (defect introduced: Postfix 2.4, date: 20051222): null + pointer read crash while parsing a malformed Dovecot AUTH + server response. Reported by Qualys, assisted by Claude + Mythos Preview. File: xsasl_dovecot_server.c. + + Bug (defect introduced: Postfix 1.0, date: 20000928): the + fast flush server, used by the SMTP command "ETRN", and by + the commands "postqueue -s site" and "postqueue -i queue_id" + (and their sendmail(1) equivalents), used the wrong duplicate + suppression API, resulting in too many queue scans by the + queue manager. Reported by Qualys, assisted by Claude Mythos + Preview. File: flush.c. + + Bug (defect introduced: Postfix 1,1, date: 20010524): the + postsuper command under-read a very short queue filename. + No crash, information leak, or privilege escalation. Reported + by Qualys, assisted by Claude Mythos Preview. Files: + postsuper.c, mail_queue.h. + + Bug (defect introduced: Postfix 3.4, date: 20180805): missing + SMTP server reset of RCPT TO state after a BDAT command + error. A crafted remote SMTP client could then send a DATA + command without MAIL FROM or RCPT TO, and crash a Postfix + SMTP daemon process with a null pointer read error. Reported + by OpenAI Security. File: smtpd/smtpd.c. + + Bug (introduced: Postfix 2.2, date: 20041102): missing SMTP + server resets of MAIL FROM and RCPT TO command state after + smtpd_end_of_data_restrictions rejected a message. This + resulted in SMTP protocol state desynchronization between + the remote SMTP client and the Postfix SMTP server. + + A crafted remote SMTP client could then send RCPT TO and + DATA without MAIL FROM, and deliver a second message. Then, + smtpd_end_of_data_restrictions skipped check_recipient_access + constraints, because a recipient counter was > 1. Reported + by OpenAI Security. File: smtpd/smtpd.c. + + As reported by OpenAI Security, the failure to reset MAIL + FROM and RCPT TO state also affected Milter support (added + in Postfix 2.3). Here, after a Milter replied with "accept + this message" based on the message envelope, and + smtpd_end_of_data_restrictions rejected the message, the + Postfix SMTP server as before accepted RCPT TO and DATA + without MAIL FROM, and smtpd_end_of_data_restrictions as + before skipped check_recipient_access constraints for the + second message. Under these conditions, the Postfix Milter + client remained in the "accept this message" state, skipping + Milter policy enforcement for the second message. + + Bug (defect introduced: Postfix 3.4, date: 20180805): SMTP + server command history memory exhaustion with a large number + of very small BDAT requests. Reported by OpenAI security. + File: smtpd.c. + + Bug (defect introduced: Postfix 1.1, date: 20021116): address + verification cache poisoning. A local user could use the + postdrop command to submit an address verification probe + with envelope or message content that Postfix rejected + later, resulting in a negative address verification cache + entry for that address. On systems that enable address + verification, the negative address verification cache entry + would force the Postfix SMTP server to reject a message + that it should accept (denial of service). Problem reported + by OpenAI Security. File: postdrop.c. + + Queue hygiene: the postdrop command accepted the null record + type which the rest of Postfix ingnores. Reported by OpenAI + Security. File: postdrop.c. diff -ur --new-file /var/tmp/postfix-3.5.26/src/dns/dns_lookup.c ./src/dns/dns_lookup.c --- /var/tmp/postfix-3.5.26/src/dns/dns_lookup.c 2024-02-27 14:25:03.000000000 -0500 +++ ./src/dns/dns_lookup.c 2026-08-09 19:06:27.000000000 -0400 @@ -717,6 +717,8 @@ data_len = strlen(temp) + 1; break; case T_MX: + if (fixed->length < NS_INT16SZ) + return (DNS_RETRY); GETSHORT(pref, pos); if (dn_expand(reply->buf, reply->end, pos, temp, sizeof(temp)) < 0) return (DNS_RETRY); diff -ur --new-file /var/tmp/postfix-3.5.26/src/flush/flush.c ./src/flush/flush.c --- /var/tmp/postfix-3.5.26/src/flush/flush.c 2023-01-15 17:31:40.000000000 -0500 +++ ./src/flush/flush.c 2026-08-09 19:06:27.000000000 -0400 @@ -571,8 +571,9 @@ STR(queue_id), path); continue; } + /* 202606 Qualys+Mythos: use htable_locate(), not htable_find(). */ if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE - || htable_find(dup_filter, STR(queue_id)) == 0) { + || htable_locate(dup_filter, STR(queue_id)) == 0) { if (msg_verbose) msg_info("%s: logfile %s: update queue file %s time stamps", myname, path, STR(queue_id)); diff -ur --new-file /var/tmp/postfix-3.5.26/src/global/mail_queue.h ./src/global/mail_queue.h --- /var/tmp/postfix-3.5.26/src/global/mail_queue.h 2020-01-19 20:04:05.000000000 -0500 +++ ./src/global/mail_queue.h 2026-08-09 19:06:27.000000000 -0400 @@ -105,12 +105,15 @@ (((cp) = strrchr((path), MQID_LG_INUM_SEP)) != 0 \ && ((cp) - (path) >= MQID_LG_TIME_PAD)) +/* 202607 Qualys+Mythos: skip short paths. */ #define MQID_GET_INUM(path, inum, long_form, error) do { \ char *_cp; \ if (((long_form) = MQID_FIND_LG_INUM_SEPARATOR(_cp, (path))) != 0) { \ MQID_LG_DECODE_INUM(_cp + 1, (inum), (error)); \ } else { \ - MQID_SH_DECODE_INUM((path) + MQID_SH_USEC_PAD, (inum), (error)); \ + if (strlen(path) > MQID_SH_USEC_PAD) \ + MQID_SH_DECODE_INUM((path) + MQID_SH_USEC_PAD, (inum), (error)); \ + else { (errno) = EINVAL; error = 1; } \ } \ } while (0) diff -ur --new-file /var/tmp/postfix-3.5.26/src/global/record.c ./src/global/record.c --- /var/tmp/postfix-3.5.26/src/global/record.c 2021-07-24 19:07:44.000000000 -0400 +++ ./src/global/record.c 2026-08-09 19:06:27.000000000 -0400 @@ -238,7 +238,7 @@ const char *myname = "rec_get"; int type; ssize_t len; - int len_byte; + ssize_t len_byte; unsigned shift; /* @@ -261,7 +261,7 @@ * limit. */ for (len = 0, shift = 0; /* void */ ; shift += 7) { - if (shift >= (int) (NBBY * sizeof(int))) { + if (shift +7 >= (int) (NBBY * sizeof(int))) { msg_warn("%s: too many length bits, record type %d", VSTREAM_PATH(stream), type); return (REC_TYPE_ERROR); diff -ur --new-file /var/tmp/postfix-3.5.26/src/postdrop/Makefile.in ./src/postdrop/Makefile.in --- /var/tmp/postfix-3.5.26/src/postdrop/Makefile.in 2019-01-29 17:24:42.000000000 -0500 +++ ./src/postdrop/Makefile.in 2026-08-09 19:12:06.000000000 -0400 @@ -63,6 +63,8 @@ postdrop.o: ../../include/check_arg.h postdrop.o: ../../include/clean_env.h postdrop.o: ../../include/cleanup_user.h +postdrop.o: ../../include/deliver_request.h +postdrop.o: ../../include/dsn.h postdrop.o: ../../include/htable.h postdrop.o: ../../include/iostuff.h postdrop.o: ../../include/mail_conf.h @@ -76,11 +78,13 @@ postdrop.o: ../../include/mail_version.h postdrop.o: ../../include/maillog_client.h postdrop.o: ../../include/msg.h +postdrop.o: ../../include/msg_stats.h postdrop.o: ../../include/msg_vstream.h postdrop.o: ../../include/mymalloc.h postdrop.o: ../../include/nvtable.h postdrop.o: ../../include/rec_attr_map.h postdrop.o: ../../include/rec_type.h +postdrop.o: ../../include/recipient_list.h postdrop.o: ../../include/record.h postdrop.o: ../../include/stringops.h postdrop.o: ../../include/sys_defs.h diff -ur --new-file /var/tmp/postfix-3.5.26/src/postdrop/postdrop.c ./src/postdrop/postdrop.c --- /var/tmp/postfix-3.5.26/src/postdrop/postdrop.c 2026-07-06 12:15:00.000000000 -0400 +++ ./src/postdrop/postdrop.c 2026-08-09 19:06:27.000000000 -0400 @@ -131,6 +131,7 @@ /* Global library. */ +#include #include #include #include @@ -423,7 +424,7 @@ } if (rec_type == REC_TYPE_ERROR) msg_fatal("uid=%ld: malformed input", (long) uid); - if (strchr(*expected, rec_type) == 0) + if (rec_type == 0 || strchr(*expected, rec_type) == 0) msg_fatal("uid=%ld: unexpected record type: %d", (long) uid, rec_type); if (rec_type == **expected) expected++; @@ -445,6 +446,19 @@ } #define STREQ(x,y) (strcmp(x,y) == 0) + /* 202607 OpenAI: allow only sendmail '-v' and '-bv' tracing. */ + if (STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { + int tflags = atoi(attr_value); + + if (tflags == DEL_REQ_FLAG_USR_VRFY + || tflags == DEL_REQ_FLAG_RECORD) + rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%d", + attr_name, tflags); + else + msg_warn("uid=%ld: ignoring unexpected trace flags: %.200s", + (long) uid, attr_value); + continue; + } if ((STREQ(attr_name, MAIL_ATTR_ENCODING) && (STREQ(attr_value, MAIL_ATTR_ENC_7BIT) || STREQ(attr_value, MAIL_ATTR_ENC_8BIT) @@ -454,8 +468,7 @@ || rec_attr_map(attr_name) || (STREQ(attr_name, MAIL_ATTR_RWR_CONTEXT) && (STREQ(attr_value, MAIL_ATTR_RWR_LOCAL) - || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE))) - || STREQ(attr_name, MAIL_ATTR_TRACE_FLAGS)) { /* XXX */ + || STREQ(attr_value, MAIL_ATTR_RWR_REMOTE)))) { rec_fprintf(dst->stream, REC_TYPE_ATTR, "%s=%s", attr_name, attr_value); } else { diff -ur --new-file /var/tmp/postfix-3.5.26/src/postscreen/postscreen_dnsbl.c ./src/postscreen/postscreen_dnsbl.c --- /var/tmp/postfix-3.5.26/src/postscreen/postscreen_dnsbl.c 2020-11-04 16:16:47.000000000 -0500 +++ ./src/postscreen/postscreen_dnsbl.c 2026-08-09 19:06:27.000000000 -0400 @@ -196,11 +196,14 @@ _cb_->context = (ctx); \ } while (0) +/* 20260606 Qualys+Mythos: read after free, neutralized by memset(0xff). */ #define PSC_CALL_BACK_NOTIFY(sp, ev) do { \ PSC_CALL_BACK_ENTRY *_cb_; \ - for (_cb_ = (sp)->table; _cb_ < (sp)->table + (sp)->index; _cb_++) \ + PSC_CALL_BACK_ENTRY *_end_ = (sp)->table + (sp)->index; \ + int _todo_ = (sp)->refcount; \ + for (_cb_ = (sp)->table; _todo_ > 0 && _cb_ < _end_; _cb_++) \ if (_cb_->callback != 0) \ - _cb_->callback((ev), _cb_->context); \ + { _cb_->callback((ev), _cb_->context); _todo_ -= 1; } \ } while (0) #define PSC_NULL_EVENT (0) @@ -311,7 +314,8 @@ if (inet_pton(AF_INET, *cpp, addr_buf) != 1) msg_warn("address conversion error for %s -- ignoring this reply", *cpp); - if (ip_match_execute(filter, addr_buf)) + /* Qualys+Mythos: skip ip_match_execute() after inet_pton() failure. */ + else if (ip_match_execute(filter, addr_buf)) return (1); } return (0); diff -ur --new-file /var/tmp/postfix-3.5.26/src/postscreen/postscreen_haproxy.c ./src/postscreen/postscreen_haproxy.c --- /var/tmp/postfix-3.5.26/src/postscreen/postscreen_haproxy.c 2020-01-12 13:02:58.000000000 -0500 +++ ./src/postscreen/postscreen_haproxy.c 2026-08-09 19:06:27.000000000 -0400 @@ -87,6 +87,11 @@ int non_proxy = 0; switch (event) { + /* Qualys+Mythos: handle I/O exception as error. */ + default: + msg_warn("haproxy read: unexpected event %d", event); + status = -1; + break; case EVENT_TIME: msg_warn("haproxy read: time limit exceeded"); status = -1; diff -ur --new-file /var/tmp/postfix-3.5.26/src/postsuper/postsuper.c ./src/postsuper/postsuper.c --- /var/tmp/postfix-3.5.26/src/postsuper/postsuper.c 2020-03-08 10:42:55.000000000 -0400 +++ ./src/postsuper/postsuper.c 2026-08-09 19:06:27.000000000 -0400 @@ -1042,6 +1042,8 @@ * number. */ if ((action & ACTION_STRUCT) + /* 202607 Qualys+Mythos: skip short paths. */ + && strlen(path) > SUFFIX_LEN && strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) { path[strlen(path) - SUFFIX_LEN] = 0; /* XXX */ if (!mail_queue_id_ok(path)) { diff -ur --new-file /var/tmp/postfix-3.5.26/src/smtpd/smtpd.c ./src/smtpd/smtpd.c --- /var/tmp/postfix-3.5.26/src/smtpd/smtpd.c 2026-07-06 12:02:55.000000000 -0400 +++ ./src/smtpd/smtpd.c 2026-08-09 19:06:27.000000000 -0400 @@ -3635,13 +3635,10 @@ && SMTPD_STAND_ALONE(state) == 0 && (err = smtpd_check_eod(state)) != 0) { smtpd_chat_reply(state, "%s", err); - if (proxy) { - smtpd_proxy_close(state); - } else { - mail_stream_cleanup(state->dest); - state->dest = 0; - state->cleanup = 0; - } + /* 202607 OpenAI: reset state like normal end-of-data. */ + chat_reset(state, var_smtpd_hist_thrsh); + mail_reset(state); + rcpt_reset(state); return (-1); } @@ -3861,9 +3858,12 @@ /* * Reset state, or drop subsequent BDAT payloads until BDAT LAST or RSET. */ - if (final_chunk) + if (final_chunk) { + chat_reset(state, var_smtpd_hist_thrsh); mail_reset(state); - else + /* 202607 OpenAI: also reset recipient state. */ + rcpt_reset(state); + } else state->bdat_state = SMTPD_BDAT_STAT_ERROR; return (-1); } @@ -5772,6 +5772,8 @@ for (;;) { if (state->flags & SMTPD_FLAG_HANGUP) break; + /* Flush the command history if it becomes large. */ + chat_reset(state, var_smtpd_hist_thrsh); if (state->error_count >= var_smtpd_hard_erlim) { state->reason = REASON_ERROR_LIMIT; state->error_mask |= MAIL_ERROR_PROTOCOL; diff -ur --new-file /var/tmp/postfix-3.5.26/src/util/inet_connect.c ./src/util/inet_connect.c --- /var/tmp/postfix-3.5.26/src/util/inet_connect.c 2022-04-18 11:53:13.000000000 -0400 +++ ./src/util/inet_connect.c 2026-08-09 19:06:27.000000000 -0400 @@ -110,8 +110,9 @@ * Safety net. */ if (strchr((char *) proto_info->sa_family_list, res->ai_family) == 0) { + /* 202606 Qualys+Mythos: myfree() above frees 'host' and 'port'. */ msg_info("skipping address family %d for host %s", - res->ai_family, host); + res->ai_family, addr); continue; } found++; diff -ur --new-file /var/tmp/postfix-3.5.26/src/util/sock_addr.c ./src/util/sock_addr.c --- /var/tmp/postfix-3.5.26/src/util/sock_addr.c 2014-12-06 20:35:32.000000000 -0500 +++ ./src/util/sock_addr.c 2026-08-09 19:06:27.000000000 -0400 @@ -118,7 +118,10 @@ * sequence would invalidate the use of memcmp(). */ if (sa->sa_family == AF_INET) { - return (SOCK_ADDR_IN_ADDR(sa).s_addr - SOCK_ADDR_IN_ADDR(sb).s_addr); + /* Qualys+Mythos 202606: (int=unsigned-unsigned) is non-transitive. */ + return (memcmp((void *) &SOCK_ADDR_IN_ADDR(sa).s_addr, + (void *) &SOCK_ADDR_IN_ADDR(sb).s_addr, + sizeof(SOCK_ADDR_IN_ADDR(sa).s_addr))); #ifdef HAS_IPV6 } else if (sa->sa_family == AF_INET6) { return (memcmp((void *) &(SOCK_ADDR_IN6_ADDR(sa)), diff -ur --new-file /var/tmp/postfix-3.5.26/src/xsasl/xsasl_dovecot_server.c ./src/xsasl/xsasl_dovecot_server.c --- /var/tmp/postfix-3.5.26/src/xsasl/xsasl_dovecot_server.c 2024-02-27 14:18:43.000000000 -0500 +++ ./src/xsasl/xsasl_dovecot_server.c 2026-08-09 19:06:27.000000000 -0400 @@ -311,7 +311,7 @@ cmd = line; line = split_at(line, '\t'); - if (strcmp(cmd, "VERSION") == 0) { + if (strcmp(cmd, "VERSION") == 0 && line != NULL) { if (sscanf(line, "%u\t%u", &major_version, &minor_version) != 2) { msg_warn("SASL: Protocol version error"); break;