]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.11.6 postfix-3.11 v3.11.6
authorWietse Z Venema <wietse@porcupine.org>
Sun, 9 Aug 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Tue, 11 Aug 2026 06:35:38 +0000 (16:35 +1000)
15 files changed:
postfix/HISTORY
postfix/src/dns/dns_lookup.c
postfix/src/flush/flush.c
postfix/src/global/mail_queue.h
postfix/src/global/mail_version.h
postfix/src/global/record.c
postfix/src/postdrop/Makefile.in
postfix/src/postdrop/postdrop.c
postfix/src/postscreen/postscreen_dnsbl.c
postfix/src/postscreen/postscreen_haproxy.c
postfix/src/postsuper/postsuper.c
postfix/src/smtpd/smtpd.c
postfix/src/util/inet_connect.c
postfix/src/util/sock_addr.c
postfix/src/xsasl/xsasl_dovecot_server.c

index 8e5ed5f389e1fd0870176566728116cff02be5e0..a800eadf47a3b0fc817bd7b25f7a39689729b589 100644 (file)
@@ -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.
index d2db3188c166703e0a0b8a12935a7c78b6ca669d..48af54c70788484ba7caf66f6facbf03e7a8a2c2 100644 (file)
@@ -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);
index b8fae7788d9b008e905d68d6d250fef43944af77..86e51ad1dba0db4ecded2baf5c07a6ab2c93e74c 100644 (file)
@@ -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));
index 1a6c66c207a9015f5bda20595ac92d20a2480876..a72cd59b6ea4cd895ccf732df22f7335541ddeaa 100644 (file)
@@ -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)
 
index eb602025d419166a887939fd5bbf2b3b0eb0d99f..cd6de7ea26cd7a6a5c5062b06f458f401c2ab3a0 100644 (file)
@@ -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
index 80cb1ac3b3c7e3cde7c56ceb720bd09e11832d21..c8b72302b3bf731c201a524e239b243e38d4330e 100644 (file)
@@ -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);
index a6d44fedd351b369cb3300cf02bd14dc2cf5765b..804a7fd5cb168c0163064ede015cdb6b0c449b23 100644 (file)
@@ -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
index 28c89748c5f7106a3b09f271c6ad23c700d707a7..a76efb76aaa6f108586c8a4c0f163b5c6bf06d72 100644 (file)
 
 /* Global library. */
 
+#include <deliver_request.h>
 #include <mail_proto.h>
 #include <mail_queue.h>
 #include <mail_params.h>
@@ -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 {
index 4be962221fa7567a324c275e526fe751aba87ce0..f560f3052b07bf269a59d7a671caa085b5be0695 100644 (file)
@@ -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);
index 45c6b9a90bca163a27a9c729b43b9f609111da9a..59d9536775f7124de72d92d659b0d1ac5d57e74b 100644 (file)
@@ -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;
index c9b8e38c67710a0db37df6e598db284671bd2280..2d2b5499ea5ce11db7334b9203d45271fbe11a4a 100644 (file)
@@ -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)) {
index ef1bc14777454198e41ada7f97a5c88e23e557de..c3bd492762da425069e4e83fa72ba094df683217 100644 (file)
@@ -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) {
index 0f5542e947c23687a8dd82a3f722b1caf0b4fc45..d58a200817a9710762ea132cd0b321af290344e8 100644 (file)
@@ -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++;
index 52a7e3e828be2d3658b89e09960a8fd3e5875a7a..c9dfc1dc27e1e0dd2c409001248885e79b2c6941 100644 (file)
@@ -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)),
index e9c16466c1df2d252f5772995bec39e5dfefa99b..b93cd1bf9656cda241ee26ab13e4f6b1b539b278 100644 (file)
@@ -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;