Prereq: "3.8.18" diff -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/global/mail_version.h ./src/global/mail_version.h --- /var/tmp/postfix-3.8.18/src/global/mail_version.h 2026-06-17 13:24:29.000000000 -0400 +++ ./src/global/mail_version.h 2026-06-28 17:49:01.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 "20260617" -#define MAIL_VERSION_NUMBER "3.8.18" +#define MAIL_RELEASE_DATE "20260706" +#define MAIL_VERSION_NUMBER "3.8.19" #ifdef SNAPSHOT #define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE diff -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/HISTORY ./HISTORY --- /var/tmp/postfix-3.8.18/HISTORY 2026-06-17 13:24:02.000000000 -0400 +++ ./HISTORY 2026-07-06 10:42:40.000000000 -0400 @@ -27870,3 +27870,124 @@ 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. Files: postlogd.c, dgram_server.c. + +20260621 + + 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. + +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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/cleanup/cleanup_extracted.c ./src/cleanup/cleanup_extracted.c --- /var/tmp/postfix-3.8.18/src/cleanup/cleanup_extracted.c 2014-11-19 14:39:48.000000000 -0500 +++ ./src/cleanup/cleanup_extracted.c 2026-06-28 17:48:24.000000000 -0400 @@ -107,6 +107,8 @@ 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 @@ 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; } } @@ -236,24 +239,25 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/global/dict_ldap.c ./src/global/dict_ldap.c --- /var/tmp/postfix-3.8.18/src/global/dict_ldap.c 2023-04-16 17:17:01.000000000 -0400 +++ ./src/global/dict_ldap.c 2026-06-28 17:48:24.000000000 -0400 @@ -1183,8 +1183,10 @@ break; } - if (resloop != 0) + if (resloop != 0) { ldap_msgfree(resloop); + resloop = 0; + } if (dict_ldap->dict.error != 0) break; diff -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/local/include.c ./src/local/include.c --- /var/tmp/postfix-3.8.18/src/local/include.c 2011-07-28 15:40:40.000000000 -0400 +++ ./src/local/include.c 2026-06-28 17:48:24.000000000 -0400 @@ -93,6 +93,15 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/local/recipient.c ./src/local/recipient.c --- /var/tmp/postfix-3.8.18/src/local/recipient.c 2017-01-09 17:49:34.000000000 -0500 +++ ./src/local/recipient.c 2026-07-05 19:06:23.000000000 -0400 @@ -217,6 +217,20 @@ 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. */ if (been_here(state.dup_filter, "recipient %d %s", diff -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/master/dgram_server.c ./src/master/dgram_server.c --- /var/tmp/postfix-3.8.18/src/master/dgram_server.c 2021-12-19 09:48:14.000000000 -0500 +++ ./src/master/dgram_server.c 2026-06-28 17:48:24.000000000 -0400 @@ -269,7 +269,7 @@ /* void */ ; if (dgram_server_in_flow_delay && mail_flow_get(1) < 0) doze(var_in_flow_delay * 1000000); - if ((len = recv(fd, buf, sizeof(buf), 0)) >= 0) + if ((len = recv(fd, buf, sizeof(buf) - 1, 0)) >= 0) dgram_server_service(buf, len, dgram_server_name, dgram_server_argv); if (master_notify(var_pid, dgram_server_generation, MASTER_STAT_AVAIL) < 0) dgram_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT); diff -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/milter/milter8.c ./src/milter/milter8.c --- /var/tmp/postfix-3.8.18/src/milter/milter8.c 2026-02-18 14:33:15.000000000 -0500 +++ ./src/milter/milter8.c 2026-07-05 19:31:45.000000000 -0400 @@ -491,6 +491,7 @@ #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 @@ return (milter8_comm_error(milter)); } *data_len = 0; + /* Qualys+Mythos: terminate string data before stale data. */ + VSTRING_TERMINATE(buf); break; /* @@ -1309,10 +1312,11 @@ } } 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/postdrop/postdrop.c ./src/postdrop/postdrop.c --- /var/tmp/postfix-3.8.18/src/postdrop/postdrop.c 2021-12-19 09:44:01.000000000 -0500 +++ ./src/postdrop/postdrop.c 2026-06-28 17:51:14.000000000 -0400 @@ -64,7 +64,7 @@ /* The default location of the Postfix main.cf and master.cf /* configuration files. /* .IP "\fBimport_environment (see 'postconf -d' output)\fR" -/* The list of environment parameters that a privileged Postfix +/* The list of environment variables that a privileged Postfix /* process will import from a non-Postfix parent process, or name=value /* environment overrides. /* .IP "\fBqueue_directory (see 'postconf -d' output)\fR" @@ -503,8 +503,10 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/postlogd/postlogd.c ./src/postlogd/postlogd.c --- /var/tmp/postfix-3.8.18/src/postlogd/postlogd.c 2022-03-30 15:56:35.000000000 -0400 +++ ./src/postlogd/postlogd.c 2026-06-28 17:48:24.000000000 -0400 @@ -152,6 +152,9 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/postscreen/postscreen_smtpd.c ./src/postscreen/postscreen_smtpd.c --- /var/tmp/postfix-3.8.18/src/postscreen/postscreen_smtpd.c 2022-05-01 15:00:08.000000000 -0400 +++ ./src/postscreen/postscreen_smtpd.c 2026-06-28 17:48:24.000000000 -0400 @@ -839,9 +839,10 @@ /* * 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/showq/showq.c ./src/showq/showq.c --- /var/tmp/postfix-3.8.18/src/showq/showq.c 2025-10-23 19:58:14.000000000 -0400 +++ ./src/showq/showq.c 2026-06-28 17:48:24.000000000 -0400 @@ -178,8 +178,10 @@ /* * 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); \ @@ -190,7 +192,8 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/smtpd/smtpd.c ./src/smtpd/smtpd.c --- /var/tmp/postfix-3.8.18/src/smtpd/smtpd.c 2026-06-17 13:25:02.000000000 -0400 +++ ./src/smtpd/smtpd.c 2026-06-28 17:48:24.000000000 -0400 @@ -3651,10 +3651,11 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/tlsmgr/tlsmgr.c ./src/tlsmgr/tlsmgr.c --- /var/tmp/postfix-3.8.18/src/tlsmgr/tlsmgr.c 2024-02-27 11:03:39.000000000 -0500 +++ ./src/tlsmgr/tlsmgr.c 2026-06-28 17:48:24.000000000 -0400 @@ -283,7 +283,7 @@ */ #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 @@ * 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 @@ 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 -ur --new-file --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL /var/tmp/postfix-3.8.18/src/util/mymalloc.c ./src/util/mymalloc.c --- /var/tmp/postfix-3.8.18/src/util/mymalloc.c 2020-07-19 11:59:20.000000000 -0400 +++ ./src/util/mymalloc.c 2026-06-28 17:48:24.000000000 -0400 @@ -130,6 +130,12 @@ #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 * shared: @@ -140,6 +146,8 @@ * - 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[] = ""; @@ -153,6 +161,15 @@ 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 * caught up-stream. @@ -213,7 +230,7 @@ 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 }