From: Wietse Z Venema Date: Sun, 9 Aug 2026 05:00:00 +0000 (-0500) Subject: postfix-3.11.6 X-Git-Tag: v3.11.6^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=85501f972b34faed783fc3de87203aba949cfff9;p=thirdparty%2Fpostfix.git postfix-3.11.6 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index 8e5ed5f38..a800eadf4 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -31004,3 +31004,117 @@ Apologies for any names omitted. 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. This behavior was later copied with SRV records, + potentially over-reading up to six bytes. 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 --git a/postfix/src/dns/dns_lookup.c b/postfix/src/dns/dns_lookup.c index d2db3188c..48af54c70 100644 --- a/postfix/src/dns/dns_lookup.c +++ b/postfix/src/dns/dns_lookup.c @@ -780,6 +780,8 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply, data_len = strlen(temp) + 1; break; case T_SRV: + if (fixed->length < 3 * NS_INT16SZ) + return (DNS_RETRY); GETSHORT(pref, pos); GETSHORT(weight, pos); GETSHORT(port, pos); @@ -792,6 +794,8 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply, 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 --git a/postfix/src/flush/flush.c b/postfix/src/flush/flush.c index b8fae7788..86e51ad1d 100644 --- a/postfix/src/flush/flush.c +++ b/postfix/src/flush/flush.c @@ -571,8 +571,9 @@ static int flush_send_path(const char *path, int how) 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 --git a/postfix/src/global/mail_queue.h b/postfix/src/global/mail_queue.h index 1a6c66c20..a72cd59b6 100644 --- a/postfix/src/global/mail_queue.h +++ b/postfix/src/global/mail_queue.h @@ -105,12 +105,15 @@ extern int mail_queue_id_ok(const char *); (((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 --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index eb602025d..cd6de7ea2 100644 --- a/postfix/src/global/mail_version.h +++ b/postfix/src/global/mail_version.h @@ -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.11.5" +#define MAIL_RELEASE_DATE "20260809" +#define MAIL_VERSION_NUMBER "3.11.6" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff --git a/postfix/src/global/record.c b/postfix/src/global/record.c index 80cb1ac3b..c8b72302b 100644 --- a/postfix/src/global/record.c +++ b/postfix/src/global/record.c @@ -238,7 +238,7 @@ int rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags) const char *myname = "rec_get"; int type; ssize_t len; - int len_byte; + ssize_t len_byte; unsigned shift; /* @@ -261,7 +261,7 @@ int rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags) * 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 --git a/postfix/src/postdrop/Makefile.in b/postfix/src/postdrop/Makefile.in index a6d44fedd..804a7fd5c 100644 --- a/postfix/src/postdrop/Makefile.in +++ b/postfix/src/postdrop/Makefile.in @@ -52,7 +52,9 @@ postdrop.o: ../../include/attr.h 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/dict.h +postdrop.o: ../../include/dsn.h postdrop.o: ../../include/htable.h postdrop.o: ../../include/iostuff.h postdrop.o: ../../include/login_sender_match.h @@ -67,6 +69,7 @@ postdrop.o: ../../include/mail_task.h 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/myflock.h postdrop.o: ../../include/mymalloc.h @@ -74,6 +77,7 @@ postdrop.o: ../../include/mypwd.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 --git a/postfix/src/postdrop/postdrop.c b/postfix/src/postdrop/postdrop.c index 28c89748c..a76efb76a 100644 --- a/postfix/src/postdrop/postdrop.c +++ b/postfix/src/postdrop/postdrop.c @@ -145,6 +145,7 @@ /* Global library. */ +#include #include #include #include @@ -512,7 +513,7 @@ int main(int argc, char **argv) } 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++; @@ -536,6 +537,19 @@ int main(int argc, char **argv) } #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) @@ -545,8 +559,7 @@ int main(int argc, char **argv) || 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 --git a/postfix/src/postscreen/postscreen_dnsbl.c b/postfix/src/postscreen/postscreen_dnsbl.c index 4be962221..f560f3052 100644 --- a/postfix/src/postscreen/postscreen_dnsbl.c +++ b/postfix/src/postscreen/postscreen_dnsbl.c @@ -196,11 +196,14 @@ typedef struct { _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 @@ static int psc_dnsbl_match(const char *filter, ARGV *reply) 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 --git a/postfix/src/postscreen/postscreen_haproxy.c b/postfix/src/postscreen/postscreen_haproxy.c index 45c6b9a90..59d953677 100644 --- a/postfix/src/postscreen/postscreen_haproxy.c +++ b/postfix/src/postscreen/postscreen_haproxy.c @@ -87,6 +87,11 @@ static void psc_endpt_haproxy_event(int event, void *context) 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 --git a/postfix/src/postsuper/postsuper.c b/postfix/src/postsuper/postsuper.c index c9b8e38c6..2d2b5499e 100644 --- a/postfix/src/postsuper/postsuper.c +++ b/postfix/src/postsuper/postsuper.c @@ -1053,6 +1053,8 @@ static void super(const char **queues, int action) * 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 --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index ef1bc1477..c3bd49276 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -3802,13 +3802,10 @@ static int common_post_message_handling(SMTPD_STATE *state) && 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); } @@ -4053,9 +4050,12 @@ static int skip_bdat(SMTPD_STATE *state, off_t chunk_size, /* * 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); } @@ -6057,6 +6057,8 @@ static void smtpd_proto(SMTPD_STATE *state) for (;;) { if (state->flags & SMTPD_FLAG_HANGUP) break; + /* Flush the command history if it becomes large. */ + chat_reset(state, var_smtpd_hist_thrsh); smtp_stream_setup(state->client, var_smtpd_tmout, var_smtpd_req_deadline, 0); if (state->error_count >= var_smtpd_hard_erlim) { diff --git a/postfix/src/util/inet_connect.c b/postfix/src/util/inet_connect.c index 0f5542e94..d58a20081 100644 --- a/postfix/src/util/inet_connect.c +++ b/postfix/src/util/inet_connect.c @@ -115,8 +115,9 @@ int inet_connect(const char *addr, int block_mode, int timeout) * 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 --git a/postfix/src/util/sock_addr.c b/postfix/src/util/sock_addr.c index 52a7e3e82..c9dfc1dc2 100644 --- a/postfix/src/util/sock_addr.c +++ b/postfix/src/util/sock_addr.c @@ -120,7 +120,10 @@ int sock_addr_cmp_addr(const struct sockaddr *sa, * 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 --git a/postfix/src/xsasl/xsasl_dovecot_server.c b/postfix/src/xsasl/xsasl_dovecot_server.c index e9c16466c..b93cd1bf9 100644 --- a/postfix/src/xsasl/xsasl_dovecot_server.c +++ b/postfix/src/xsasl/xsasl_dovecot_server.c @@ -313,7 +313,7 @@ static int xsasl_dovecot_server_connect(XSASL_DOVECOT_SERVER_IMPL *xp) 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;