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.
char *attr_value;
const char *error_text;
int extra_opts;
+ int mapped_type = type;
+ const char *mapped_buf = buf;
int junk;
#ifdef DELAY_ACTION
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;
}
}
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);
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
break;
}
- if (resloop != 0)
+ if (resloop != 0) {
ldap_msgfree(resloop);
+ resloop = 0;
+ }
if (dict_ldap->dict.error != 0)
break;
* 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
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
*
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.
*/
#define STR(x) vstring_str(x)
#define LEN(x) VSTRING_LEN(x)
+#define END(x) vstring_end(x)
/* milter8_def_reply - set persistent response */
return (milter8_comm_error(milter));
}
*data_len = 0;
+ /* Qualys+Mythos: terminate string data before stale data. */
+ VSTRING_TERMINATE(buf);
break;
/*
}
}
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)
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)
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;
}
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
/*
* 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,
/*
* 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); \
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
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]))
/* 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 : "");
*/
#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)
* 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);
}
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,
#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
* - 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[] = "";
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
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
}
{
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));
}