From: Wietse Z Venema Date: Mon, 6 Jul 2026 05:00:00 +0000 (-0500) Subject: postfix-3.11.5 X-Git-Tag: v3.11.5^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e84bd12609af52127ebbc90916502550eee9d59d;p=thirdparty%2Fpostfix.git postfix-3.11.5 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index e2b3ba794..8e5ed5f38 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -30865,3 +30865,142 @@ Apologies for any names omitted. could reject or discard the parameter value, but this has never been reported to happen. Found during code maintenance. File: smtp_proto.c. + +20260618 + + Hardening: make sure that optimizers will not delete a + memset() call in myfree() that wipes memory. File: mymalloc.c. + + Bug (defect introduced: Postfix 3.1, date: 20151129): a + missing return statement in the SHOWQ_CLEANUP_AND_RETURN() + macro. A local user could submit a crafted message that + triggered a read-after-free and panic() in the unprivileged + showq daemon (which scans the mail queue for the 'postqueue + -f' and 'mailq' commands). This could happen only before a + message had been picked up by the pickup(8) daemon. Problem + reported by Qualys, assisted by Claude Mythos Preview. File: + showq.c. + +20260619 + + Bug (defect introduced: Postfix 3.4, date: 20190121): missing + null termination in a postlogd process that was started + with an EMPTY maillog_file setting, while receiving a message + from a postlog command that was started with a NON-EMPTY + maillog_file setting. Under these contradicting conditions, + an unprivileged attacker could cause postlogd to write null + bytes to stack memory as it tokenized text outside the + receive buffer, and possibly gain 'postfix' privilege. + Problem reported by Qualys, assisted by Claude Mythos + Preview. File: postlogd.c. + +20260621 + + Bug (defect introduced: postfix-3.11.0-RC1, date: 20251222): + heap memory over-read in the cleanup daemon as it handled + a milter "shutdown" reply. The over-read memory was logged + after masking unprintable content. Problem reported by + Qualys, assisted by Claude Mythos Preview. File: cleanup_milter.c. + + Bug (defect introduced: Postfix 2.3, date: 20050526): limited + (<= 11 byte) heap over-read in the cleanup daemon. This + could be triggered by local user with a crafted queue file, + but the over-read content was not disclosed and there was + no other impact. Problem reported by Qualys, assisted by + Claude Mythos Preview. File: cleanup_extracted.c. + + Maintainer future proofing: allow zero-length memory + allocation requests. Many people have experience with systems + that allow this, therefore it should not trigger a panic + in Postfix. File: mymalloc.c. + +20260623 + + Bug (defect introduced: Postfix 2.3, date: 20060611): double + ldap_msgfree(resloop) call during error handling when + special_result_attribute is configured. An attacker who + controls the LDAP server or can play attacker-in-the-middle + could corrupt heap memory. Reported by Qualys, assisted by + Claude Mythos Preview. File: dict_ldap.c. + +20260624 + + Bug (defect introduced: Postfix < alpha, date: 1997): missing + recursion guard while processing :include: files that + directly include other :include: files in local(8) aliases + or .forward files. This could result in exhausting stack + space (segfault) or file handles (fatal error). This is not + a global DOS; it affected at most two parallel delivery + processes for the local recipient who created the condition. + Reported by Qualys, assisted by Claude Mythos Preview. File: + local/include.c. + + Safety: added a global nesting guard. File: local/recipient.c. + +20260625 + + Bug (defect introduced: Postfix 2.7, date: 20090617): + out-of-memory condition with remote input in the postscreen + dummy SMTP engine. This dummy engine is used after PREGREET + or DNSBL checks fail, or when "after 220" protocol checks + are enabled. Reported by Qualys, assisted by Claude Mythos + Preview. File: postscreen_smtpd.c. + + Bug (defect introduced: Postfix 2.0, date: 20030619): file + system DOS: with smtpd_proxy_filter enabled, the before-filter + SMTP server did not enforce the message size limit for + mailbox From_ lines at the beginning of a message. With + smtpd_proxy_filter disabled, the file size limit was still + enforced by the cleanup daemon. Reported by Qualys, assisted + by Claude Mythos Preview. File: smtpd.c. + + Bug (defect introduced: Postfix 2.1, date: 20030619): SMTP + server panic() in smtpd_proxy_filter when handling long + mailbox From_ lines at the beginning of a message. Reported + by Qualys, assisted by Claude Mythos Preview. File: smtpd.c. + + Bug: (defect introduced: Postfix 3.10, date: 20240925): + NULL pointer read in the TLSRPT client, caused by missing + STR_OR_NULL() wrappers. Reported by Qualys, assisted by + Claude Mythos Preview. File: tlsrpt_wrapper.c. + + Bug (defect introduced: Postfix 3.11, date: 20260219): In + the non-BerkeleyDB re-indexing server, vstream_fopen_as() + ignored the uid and gid arguments and opened a database + source file read-only as the 'postfix' user instead of the + file owner. Reported by Qualys, assisted by Claude Mythos + Preview. File: open_as.c. + +20260627 + + Future proofing: use the correct device name in DEV_PATH() + macro. Reported by Qualys, assisted by Claude Mythos Preview. + File: tlsmgr.c. + + Bug (defect introduced: Postfix 2.2. date: 20040829): after + a RAND_bytes() call failure, do not rely on stack-based + pseudo-randomness for tlsmgr seed generation, and for timing + jitter of tlsmgr seed refresh intervals. Reported by Qualys, + assisted by Claude Mythos Preview. File: tlsmgr.c. + + Bug (defect introduced: Postfix 2.3, date: 20060711): In + the Milter client, null-terminate the SMFIR_REPLYCODE + response data to exclude stale data when processing the + result as a C string. Reported by Qualys, assisted by Claude + Mythos Preview. File: milter8.c. + + Bug (defect introduced: Postfix 2.3, date: 20060711): + one-byte heap over-write in the Milter client with + soft_bounce=yes while processing a malformed SMFIR_REPLYCODE + Milter response. An attacker who controls the Milter or who + can play attacker-in-the-middle could corrupt heap memory. + Reported by Qualys, assisted by Claude Mythos Preview. File: + milter8.c. + +20260628 + + Bug (defect introduced: Postfix < alpha, date: 19971221): + a signal handler in the postdrop command could call unlink() + 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. diff --git a/postfix/src/cleanup/cleanup_extracted.c b/postfix/src/cleanup/cleanup_extracted.c index a0cbc5a64..43ea36fd9 100644 --- a/postfix/src/cleanup/cleanup_extracted.c +++ b/postfix/src/cleanup/cleanup_extracted.c @@ -107,6 +107,8 @@ void cleanup_extracted_process(CLEANUP_STATE *state, int type, char *attr_value; const char *error_text; int extra_opts; + int mapped_type = type; + const char *mapped_buf = buf; int junk; #ifdef DELAY_ACTION @@ -168,9 +170,10 @@ void cleanup_extracted_process(CLEANUP_STATE *state, int type, state->queue_id, attr_name); return; } + /* 202606 Qualys+Mythos: don't clobber 'type' and 'buf'. */ if ((junk = rec_attr_map(attr_name)) != 0) { - buf = attr_value; - type = junk; + mapped_buf = attr_value; + mapped_type = junk; } } @@ -240,24 +243,25 @@ void cleanup_extracted_process(CLEANUP_STATE *state, int type, state->dsn_notify = 0; return; } - if (type == REC_TYPE_DSN_ORCPT) { + if (mapped_type == REC_TYPE_DSN_ORCPT) { if (state->dsn_orcpt) { msg_warn("%s: ignoring out-of-order DSN original recipient record <%.200s>", state->queue_id, state->dsn_orcpt); myfree(state->dsn_orcpt); } - state->dsn_orcpt = mystrdup(buf); + state->dsn_orcpt = mystrdup(mapped_buf); return; } - if (type == REC_TYPE_DSN_NOTIFY) { + if (mapped_type == REC_TYPE_DSN_NOTIFY) { if (state->dsn_notify) { msg_warn("%s: ignoring out-of-order DSN notify record <%d>", state->queue_id, state->dsn_notify); state->dsn_notify = 0; } - if (!alldig(buf) || (junk = atoi(buf)) == 0 || DSN_NOTIFY_OK(junk) == 0) + if (!alldig(mapped_buf) || (junk = atoi(mapped_buf)) == 0 + || DSN_NOTIFY_OK(junk) == 0) msg_warn("%s: ignoring malformed dsn notify record <%.200s>", - state->queue_id, buf); + state->queue_id, mapped_buf); else state->qmgr_opts |= QMGR_READ_FLAG_FROM_DSN(state->dsn_notify = junk); diff --git a/postfix/src/cleanup/cleanup_milter.c b/postfix/src/cleanup/cleanup_milter.c index 4819c712d..9c894d7e6 100644 --- a/postfix/src/cleanup/cleanup_milter.c +++ b/postfix/src/cleanup/cleanup_milter.c @@ -2077,15 +2077,9 @@ static const char *cleanup_milter_apply(CLEANUP_STATE *state, const char *event, text = "milter triggers DISCARD action"; break; case 'S': - if (state->flags & CLEANUP_STAT_CONT) - return (0); - /* Shutdown' may be the default action for an I/O error. */ - CLEANUP_MILTER_SET_SMTP_REPLY(state, resp); - ret = state->reason; - state->errs |= CLEANUP_STAT_WRITE; - action = "milter-reject"; - text = resp + 4; - break; + /* 202606 Qualys+Mythos found heap over-read at "S" + 4. */ + resp = "421 4.7.0 Service unavailable"; /* See also smtpd.c. */ + /* FALLTHROUGH */ /* * Override permanent reject with temporary reject. This happens when diff --git a/postfix/src/global/dict_ldap.c b/postfix/src/global/dict_ldap.c index 9236639a1..1e65a5179 100644 --- a/postfix/src/global/dict_ldap.c +++ b/postfix/src/global/dict_ldap.c @@ -1183,8 +1183,10 @@ static void dict_ldap_get_values(DICT_LDAP *dict_ldap, LDAPMessage *res, break; } - if (resloop != 0) + if (resloop != 0) { ldap_msgfree(resloop); + resloop = 0; + } if (dict_ldap->dict.error != 0) break; diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 40279c025..eb602025d 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 "20260617" -#define MAIL_VERSION_NUMBER "3.11.4" +#define MAIL_RELEASE_DATE "20260706" +#define MAIL_VERSION_NUMBER "3.11.5" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff --git a/postfix/src/local/include.c b/postfix/src/local/include.c index a213d3c08..da5b6a8d0 100644 --- a/postfix/src/local/include.c +++ b/postfix/src/local/include.c @@ -93,6 +93,15 @@ int deliver_include(LOCAL_STATE state, USER_ATTR usr_attr, char *path) if (msg_verbose) MSG_LOG_STATE(myname, state); + /* 202606 Qualys+Mythos: add missing nesting limit. */ + if (state.level > 100) { + msg_warn(":include: nesting limit exceeded for %s", path); + dsb_simple(state.msg_attr.why, "5.4.6", + ":include: nesting limit exceeded"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + /* * DUPLICATE ELIMINATION * diff --git a/postfix/src/local/recipient.c b/postfix/src/local/recipient.c index e3f4d1ceb..a1900d6d0 100644 --- a/postfix/src/local/recipient.c +++ b/postfix/src/local/recipient.c @@ -216,6 +216,20 @@ int deliver_recipient(LOCAL_STATE state, USER_ATTR usr_attr) if (msg_verbose) MSG_LOG_STATE(myname, state); + /* + * Global recursion safety check for loops that are not already broken + * locally, such as :include: files that directly :include: another + * file). + */ + if (state.level > 100) { + msg_warn("recipient nesting limit exceeded for %s", + state.msg_attr.rcpt.address); + dsb_simple(state.msg_attr.why, "5.4.6", + "recipient nesting limit exceeded"); + return (bounce_append(BOUNCE_FLAGS(state.request), + BOUNCE_ATTR(state.msg_attr))); + } + /* * Duplicate filter. */ diff --git a/postfix/src/milter/milter8.c b/postfix/src/milter/milter8.c index 02e3ca3cd..1abd04505 100644 --- a/postfix/src/milter/milter8.c +++ b/postfix/src/milter/milter8.c @@ -491,6 +491,7 @@ static const NAME_CODE milter8_versions[] = { #define STR(x) vstring_str(x) #define LEN(x) VSTRING_LEN(x) +#define END(x) vstring_end(x) /* milter8_def_reply - set persistent response */ @@ -683,6 +684,8 @@ static int vmilter8_read_data(MILTER8 *milter, ssize_t *data_len, va_list ap) return (milter8_comm_error(milter)); } *data_len = 0; + /* Qualys+Mythos: terminate string data before stale data. */ + VSTRING_TERMINATE(buf); break; /* @@ -1309,10 +1312,11 @@ static const char *milter8_event(MILTER8 *milter, int event, } } if (var_soft_bounce) { - for (cp = STR(milter->buf); /* void */ ; cp = next) { + for (cp = STR(milter->buf); cp < END(milter->buf) ; cp = next) { if (cp[0] == '5') { cp[0] = '4'; - if (cp[4] == '5') + /* Qualys+Mythos: add missing guard. */ + if (cp + 4 < END(milter->buf) && cp[4] == '5') cp[4] = '4'; } if ((next = strstr(cp, "\r\n")) == 0) diff --git a/postfix/src/postdrop/postdrop.c b/postfix/src/postdrop/postdrop.c index f7771ea79..28c89748c 100644 --- a/postfix/src/postdrop/postdrop.c +++ b/postfix/src/postdrop/postdrop.c @@ -504,8 +504,10 @@ int main(int argc, char **argv) msg_warn("uid=%ld: remove %s: %m", (long) uid, postdrop_path); else if (msg_verbose) msg_info("remove %s", postdrop_path); - myfree(postdrop_path); + /* Qualys+Mythos: avoid read-after-free in signal handler. */ + junk = postdrop_path; postdrop_path = 0; + myfree(junk); exit(0); } if (rec_type == REC_TYPE_ERROR) diff --git a/postfix/src/postlogd/postlogd.c b/postfix/src/postlogd/postlogd.c index f844c3d57..6e7aee5d9 100644 --- a/postfix/src/postlogd/postlogd.c +++ b/postfix/src/postlogd/postlogd.c @@ -152,10 +152,10 @@ static void postlogd_fallback(const char *buf) static void postlogd_service(int sock, char *unused_service, char **unused_argv) { - char buf[DGRAM_BUF_SIZE]; + char buf[DGRAM_BUF_SIZE + 1]; ssize_t len; - if ((len = recv(sock, buf, sizeof(buf), 0)) < 0) { + if ((len = recv(sock, buf, sizeof(buf) - 1, 0)) < 0) { msg_warn("failed to receive message with recv: %m"); return; } @@ -173,6 +173,9 @@ static void postlogd_service(int sock, char *unused_service, char *bp = buf; char *progname_pid; + /* 202606 Qualys+Mythos: null-terminate the buffer. */ + buf[len] = 0; + /* * Avoid surprises: strip off the date, time, host, and program[pid]: * prefix that were prepended by msg_logger(3). Then, hope that the diff --git a/postfix/src/postscreen/postscreen_smtpd.c b/postfix/src/postscreen/postscreen_smtpd.c index 6b72626a7..ac181d248 100644 --- a/postfix/src/postscreen/postscreen_smtpd.c +++ b/postfix/src/postscreen/postscreen_smtpd.c @@ -839,9 +839,10 @@ static void psc_smtpd_read_event(int event, void *context) /* * Sanity check. We don't want to store infinitely long commands. + * + * Qualys+Mythos: make the VSTRING_LEN test unconditional. */ - if (state->read_state == PSC_SMTPD_CMD_ST_ANY - && VSTRING_LEN(state->cmd_buffer) >= var_line_limit) { + if (VSTRING_LEN(state->cmd_buffer) >= var_line_limit) { msg_info("COMMAND LENGTH LIMIT from [%s]:%s after %s", PSC_CLIENT_ADDR_PORT(state), state->where); PSC_CLEAR_EVENT_DROP_SESSION_STATE(state, psc_smtpd_time_event, diff --git a/postfix/src/showq/showq.c b/postfix/src/showq/showq.c index 89ac9cf39..9021cea18 100644 --- a/postfix/src/showq/showq.c +++ b/postfix/src/showq/showq.c @@ -180,8 +180,10 @@ static void showq_report(VSTREAM *client, char *queue, char *id, /* * Let the optimizer worry about eliminating duplicate code. + * + * 202606 Qualys+Mythos: add missing return statement. */ -#define SHOWQ_CLEANUP_AND_RETURN { \ +#define SHOWQ_CLEANUP_AND_RETURN do { \ if (sender_seen > 0) \ attr_print(client, ATTR_FLAG_NONE, ATTR_TYPE_END); \ vstring_free(buf); \ @@ -193,7 +195,8 @@ static void showq_report(VSTREAM *client, char *queue, char *id, dsb_free(dsn_buf); \ if (dup_filter) \ htable_free(dup_filter, (void (*) (void *)) 0); \ - } + return; \ + } while (0) /* * XXX addresses in defer logfiles are in printable quoted form, while diff --git a/postfix/src/smtpd/smtpd.c b/postfix/src/smtpd/smtpd.c index d2272ee18..ef1bc1477 100644 --- a/postfix/src/smtpd/smtpd.c +++ b/postfix/src/smtpd/smtpd.c @@ -3749,10 +3749,11 @@ static void receive_data_message(SMTPD_STATE *state, start = vstring_str(state->buffer); len = VSTRING_LEN(state->buffer); if (first) { + /* Qualys+Mythos: DOS in mbox line reading loop. */ if (strncmp(start + strspn(start, ">"), "From ", 5) == 0) { - out_fprintf(out_stream, curr_rec_type, - "X-Mailbox-Line: %s", start); - continue; + /* Qualys+Mythos: panic in smtpd_proxy*rec_fprintf(). */ + out_record(out_stream, REC_TYPE_CONT, "X-Mailbox-Line: ", 16); + state->act_size += 16; } first = 0; if (len > 0 && IS_SPACE_TAB(start[0])) diff --git a/postfix/src/tls/tlsrpt_wrapper.c b/postfix/src/tls/tlsrpt_wrapper.c index 3feca68bf..194609f66 100644 --- a/postfix/src/tls/tlsrpt_wrapper.c +++ b/postfix/src/tls/tlsrpt_wrapper.c @@ -582,7 +582,8 @@ int trw_report_failure(TLSRPT_WRAPPER *trw, /* Give the local admin a clue. */ msg_info("TLSRPT: status=failure, domain=%s, receiving_mx=%s[%s]," " failure_type=%s%s%s", - trw->rpt_policy_domain, trw->rcv_mta_name, trw->rcv_mta_addr, + trw->rpt_policy_domain, STR_OR_NULL(trw->rcv_mta_name), + STR_OR_NULL(trw->rcv_mta_addr), trw_failure_type_to_string(failure_type), failure_reason ? ", failure_reason=" : "", failure_reason ? failure_reason : ""); diff --git a/postfix/src/tlsmgr/tlsmgr.c b/postfix/src/tlsmgr/tlsmgr.c index d32e63d4b..59fc95eec 100644 --- a/postfix/src/tlsmgr/tlsmgr.c +++ b/postfix/src/tlsmgr/tlsmgr.c @@ -283,7 +283,7 @@ static TLS_PRNG_SRC *rand_source_file; */ #define DEV_PREF "dev:" #define DEV_PREF_LEN (sizeof((DEV_PREF)) - 1) -#define DEV_PATH(dev) ((dev) + EGD_PREF_LEN) +#define DEV_PATH(dev) ((dev) + DEV_PREF_LEN) #define EGD_PREF "egd:" #define EGD_PREF_LEN (sizeof((EGD_PREF)) - 1) @@ -353,7 +353,8 @@ static void tlsmgr_prng_exch_event(int unused_event, void *dummy) * Make prediction difficult for outsiders and calculate the time for the * next execution randomly. */ - RAND_bytes(&randbyte, 1); + if (RAND_bytes(&randbyte, 1) < 0) + randbyte = getpid() & 0xff; next_period = (var_tls_prng_exch_period * randbyte) / UCHAR_MAX; event_request_timer(tlsmgr_prng_exch_event, dummy, next_period); } @@ -758,9 +759,12 @@ static void tlsmgr_service(VSTREAM *client_stream, char *unused_service, len, TLS_MGR_REQ_SEED); } else { VSTRING_SPACE(buffer, len); - RAND_bytes((unsigned char *) STR(buffer), len); - vstring_set_payload_size(buffer, len); - status = TLS_MGR_STAT_OK; + if (RAND_bytes((unsigned char *) STR(buffer), len) < 0) { + status = TLS_MGR_STAT_ERR; + } else { + vstring_set_payload_size(buffer, len); + status = TLS_MGR_STAT_OK; + } } } attr_print(client_stream, ATTR_FLAG_NONE, diff --git a/postfix/src/util/mymalloc.c b/postfix/src/util/mymalloc.c index 94f7bb3e7..48a0539c7 100644 --- a/postfix/src/util/mymalloc.c +++ b/postfix/src/util/mymalloc.c @@ -129,6 +129,12 @@ typedef struct MBLOCK { #define SPACE_FOR(len) (offsetof(MBLOCK, u.payload[0]) + len) + /* + * The memset-before-free safety net should not be optimized out by future + * compilers or linkers. + */ +static void *(*volatile safe_memset) (void *, int, size_t) = memset; + /* * Optimization for short strings. We share one copy with multiple callers. * This differs from normal heap memory in two ways, because the memory is @@ -140,6 +146,8 @@ typedef struct MBLOCK { * - myfree() cannot overwrite the memory with a filler pattern like it can do * with heap memory. Therefore, some dangling pointer bugs will be masked. */ +#undef NO_SHARED_EMPTY_STRINGS + #ifndef NO_SHARED_EMPTY_STRINGS static const char empty_string[] = ""; @@ -152,6 +160,15 @@ void *mymalloc(ssize_t len) void *ptr; MBLOCK *real_ptr; + /* + * Maintainer proofing: many people expect that a request for null memory + * will not result in a panic(). + */ +#ifndef NO_SHARED_EMPTY_STRINGS + if (len == 0) + return ((void *) empty_string); +#endif + /* * Note: for safety reasons the request length is a signed type. This * allows us to catch integer overflow problems that weren't already @@ -213,7 +230,7 @@ void myfree(void *ptr) if (ptr != empty_string) { #endif CHECK_IN_PTR(ptr, real_ptr, len, "myfree"); - memset((void *) real_ptr, FILLER, SPACE_FOR(len)); + safe_memset((void *) real_ptr, FILLER, SPACE_FOR(len)); free((void *) real_ptr); #ifndef NO_SHARED_EMPTY_STRINGS } diff --git a/postfix/src/util/open_as.c b/postfix/src/util/open_as.c index 744bb8161..8adecd819 100644 --- a/postfix/src/util/open_as.c +++ b/postfix/src/util/open_as.c @@ -89,7 +89,7 @@ VSTREAM *vstream_fopen_as(const char *path, int flags, mode_t mode, { int fd; - if ((fd = open(path, flags, mode)) < 0) + if ((fd = open_as(path, flags, mode, euid, egid)) < 0) return (0); return (vstream_fdopen(fd, flags)); }