recursive logging loop with "posttls-finger -v -v -v".
Reported by Geert Hendrickx, diagnosed by Viktor Dukhovni,
and fixed by Wietse. Files: util/vstream.[hc], util/msg_vstream.c.
+
+20260310
+
+ Bugfix (defect introduced: Postfix 3.0): buffer over-read
+ when Postfix is configured with an enhanced status code not
+ followed by other text. For example, "5.7.2" without text
+ after the three-number code in a remote server response,
+ in an access(5) table, header or body checks, or with
+ "$rbl_code $rbl_text" in rbl_reply_maps or default_rbl_reply.
+ Problem reported by Kamil Frankowicz. File: global/dsn_split.c.
+
+20260426
+
+ Portability: support for recent FreeBSD, NetBSD, and OpenBSD
+ versions. Brad Smith. Files: makedefs, util/sys_defs.h.
+
+20260501
+
+ Bugfix (defect introduced: Postfix 2.2, date 20041207):
+ When truncating a database file, the CDB client looked at
+ the file size from before requesting an exclusive lock on
+ a database file, instead of the file size after the exclusive
+ lock was granted. Found by Claude Opus 4.6. File:
+ util/dict_cdb.c.
+
+ Bugfix (defect introduced: Postfix alpha, date 19980309):
+ file descriptor leak after fork() failure. Found by Claude
+ Opus 4.6. File: global/pipe_command.c.
+
+ Mistakes in debug logging. Found by Claude Opus 4.6. Files:
+ util/dict_cidr.c, tls/tls_prng_file.c.
+
+ Unchecked null pointer results after an out-of-memory
+ condition in a library dependency. Found by Claude Opus
+ 4.6. Files: util/dict_pcre.c, util/midna_domain.c,
+ global/dict_pgsql.c.
+
+ Missing or incomplete guards for ssize_t or int overflow,
+ found by Claude Opus 4.6. Files: util/argv.c, util/netstring.c,
+ util/vbuf_print.c.
+
+ Cleanup: log a fatal error instead of dereferencing a null
+ pointer after a first/next cursor initialization failure.
+ Fedor Vorobev. File: util/dict_db.c.
: ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
: ${PLUGIN_LD="${CC} -shared"}
;;
+ FreeBSD.15*) SYSTYPE=FREEBSD15
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ FreeBSD.16*) SYSTYPE=FREEBSD16
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
DragonFly.*) SYSTYPE=DRAGONFLY
;;
OpenBSD.2*) SYSTYPE=OPENBSD2
: ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
: ${PLUGIN_LD="${CC} -shared"}
;;
+ OpenBSD.8*) SYSTYPE=OPENBSD8
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so.1.0}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
ekkoBSD.1*) SYSTYPE=EKKOBSD1
;;
- NetBSD.1*) SYSTYPE=NETBSD1
+ NetBSD.1.*) SYSTYPE=NETBSD1
;;
NetBSD.2*) SYSTYPE=NETBSD2
;;
: ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
: ${PLUGIN_LD="${CC-gcc} -shared"}
;;
+ NetBSD.11*) SYSTYPE=NETBSD11
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ NetBSD.12*) SYSTYPE=NETBSD12
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
BSD/OS.2*) SYSTYPE=BSDI2
;;
BSD/OS.3*) SYSTYPE=BSDI3
dict_pgsql->password);
}
if (host->db == NULL || PQstatus(host->db) != CONNECTION_OK) {
+ /* 202604 Claude: don't call PQerrorMessage(NULL). */
msg_warn("connect to pgsql server %s: %s",
- host->hostname, PQerrorMessage(host->db));
+ host->hostname, host->db ? PQerrorMessage(host->db) :
+ "PQconnectdb or PQsetdbLogin failed");
plpgsql_down_host(host, dict_pgsql->retry_interval);
return;
}
if ((len = dsn_valid(cp)) > 0) {
strncpy(dp->dsn.data, cp, len);
dp->dsn.data[len] = 0;
- cp += len + 1;
+ cp += len;
} else if ((len = dsn_valid(def_dsn)) > 0) {
strncpy(dp->dsn.data, def_dsn, len);
dp->dsn.data[len] = 0;
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20260218"
-#define MAIL_VERSION_NUMBER "3.9.9"
+#define MAIL_RELEASE_DATE "20260501"
+#define MAIL_VERSION_NUMBER "3.9.10"
#ifdef SNAPSHOT
#define MAIL_VERSION_DATE "-" MAIL_RELEASE_DATE
msg_warn("fork: %m");
dsb_unix(why, "4.3.0", sys_exits_detail(EX_OSERR)->text,
"Delivery failed: %m");
+ /* 202604 Claude: close pipes for the child and parent paths. */
+ close(cmd_in_pipe[0]);
+ close(cmd_in_pipe[1]);
+ close(cmd_out_pipe[0]);
+ close(cmd_out_pipe[1]);
return (PIPE_STAT_DEFER);
/*
RAND_seed(buffer, count);
}
if (msg_verbose)
- msg_info("read %ld bytes from entropy file %s: %m",
+ /* 202604 Claude: remove '%m' from non-error logging. */
+ msg_info("read %ld bytes from entropy file %s",
(long) (len - to_read), fh->name);
return (len - to_read);
}
argvp = (ARGV *) mymalloc(sizeof(*argvp));
argvp->len = 0;
sane_len = (len < 2 ? 2 : len);
+ /* 202604 Claude: avoid overflowing sane_len + 1 */
+ if (sane_len > SSIZE_MAX - 1)
+ msg_panic("argv_alloc: array length overflow");
argvp->argv = (char **) mymalloc((sane_len + 1) * sizeof(char *));
argvp->len = sane_len;
argvp->argc = 0;
{
ssize_t new_len;
+ /* 202604 Claude: avoid overflowing (new_len + 1) * sizeof(char *). */
+ if (argvp->len > SSIZE_MAX / (2 * sizeof(char *)) - 1)
+ msg_panic("argv_extend: array length overflow");
new_len = argvp->len * 2;
argvp->argv = (char **)
myrealloc((void *) argvp->argv, (new_len + 1) * sizeof(char *));
ssize_t pos;
/*
- * Sanity check.
+ * Sanity check. 202604 Claude: avoid expression 'first + how_many'.
*/
- if (first < 0 || how_many < 0 || first + how_many > argvp->argc)
+ if (first < 0 || how_many < 0 || first > argvp->argc
+ || how_many > argvp->argc - first)
msg_panic("argv_delete bad range: (start=%ld count=%ld)",
(long) first, (long) how_many);
}
#ifndef NO_FTRUNCATE
- if (st0.st_size)
+ /* 202604 Claude: use the post-myflock() fstat result. */
+ if (st1.st_size)
ftruncate(fd, 0);
#endif
rule->lineno = lineno;
if (msg_verbose) {
- if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
- hostaddr.buf, sizeof(hostaddr.buf)) == 0)
- msg_fatal("inet_ntop: %m");
- msg_info("dict_cidr_open: add %s/%d %s",
- hostaddr.buf, cidr_info.mask_shift, rule->value);
+ /* 202604 Claude: ENDIF has no address pattern. */
+ if (cidr_info.op != CIDR_MATCH_OP_ENDIF) {
+ if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
+ hostaddr.buf, sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ msg_info("dict_cidr_open: add %s/%d %s",
+ hostaddr.buf, cidr_info.mask_shift, rule->value);
+ }
}
return (rule);
}
*/
switch (function) {
case DICT_SEQ_FUN_FIRST:
- if (dict_db->cursor == 0)
- DICT_DB_CURSOR(db, &(dict_db->cursor));
+ if (dict_db->cursor == 0
+ && (status = DICT_DB_CURSOR(db, &(dict_db->cursor))) != 0)
+ msg_fatal("error [%d] initializing cursor for %s: %m",
+ status, dict_db->dict.name);
db_function = DB_FIRST;
break;
case DICT_SEQ_FUN_NEXT:
}
engine->match_data = pcre2_match_data_create_from_pattern(
engine->pattern, (void *) 0);
+ /* 202604 Claude: handle error result. */
+ if (engine->match_data == 0)
+ msg_fatal("out of memory in pcre2_match_data_create_from_pattern()");
#endif
return (1);
}
reply_payload = split_at(STR(dp->rdwr_buf), ' ');
if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_OK) == 0) {
dict->error = 0;
- return (reply_payload);
+ /* 202604 Claude: don't return NULL with dict->error==0. */
+ return (reply_payload ? reply_payload : "");
} else if (strcmp(STR(dp->rdwr_buf), DICT_SOCKMAP_PROT_NOTFOUND) == 0) {
dict->error = 0;
return (0);
*/
idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
: UIDNA_NONTRANSITIONAL_TO_ASCII, &error);
- anl = uidna_nameToASCII_UTF8(idna,
- name, strlen(name),
- buf, sizeof(buf) - 1,
- &info,
- &error);
+ /* 202604 Claude: avoid null deref after uidna_openUTS46() failure. */
+ if (idna && U_SUCCESS(error))
+ anl = uidna_nameToASCII_UTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
uidna_close(idna);
/*
* "fake" A-labels, as required by UTS 46 section 4.1, but we rely on
* valid_hostname() on the output side just to be sure.
*/
- if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ if (idna && U_SUCCESS(error) && info.errors == 0 && anl > 0) {
buf[anl] = 0; /* XXX */
if (!valid_hostname(buf, DONT_GRIPE)) {
msg_warn("%s: Problem translating domain \"%.100s\" to ASCII form: %s",
*/
idna = uidna_openUTS46(midna_domain_transitional ? UIDNA_DEFAULT
: UIDNA_NONTRANSITIONAL_TO_UNICODE, &error);
- anl = uidna_nameToUnicodeUTF8(idna,
- name, strlen(name),
- buf, sizeof(buf) - 1,
- &info,
- &error);
+ /* 202604 Claude: avoid null deref after uidna_openUTS46() failure. */
+ if (idna && U_SUCCESS(error))
+ anl = uidna_nameToUnicodeUTF8(idna,
+ name, strlen(name),
+ buf, sizeof(buf) - 1,
+ &info,
+ &error);
uidna_close(idna);
/*
* other invalid forms that are not covered in UTS 46, section 4.1). We
* rely on midna_domain_to_ascii() to validate the output.
*/
- if (U_SUCCESS(error) && info.errors == 0 && anl > 0) {
+ if (idna && U_SUCCESS(error) && info.errors == 0 && anl > 0) {
buf[anl] = 0; /* XXX */
if (midna_domain_to_ascii(buf) == 0)
return (0);
VA_COPY(ap2, ap);
/*
- * Figure out the total result size.
+ * Figure out the total result size. 202604 Claude: move the wrap-around
+ * guard inside the loop.
*/
- for (total = 0; (data = va_arg(ap, char *)) != 0; total += data_len)
+ for (total = 0; (data = va_arg(ap, char *)) != 0; /* see below */ ) {
if ((data_len = va_arg(ap, ssize_t)) < 0)
msg_panic("%s: bad data length %ld", myname, (long) data_len);
+ if (data_len > SSIZE_T_MAX - total)
+ msg_panic("%s: total length overflow", myname);
+ total += data_len;
+ }
va_end(ap);
- if (total < 0)
- msg_panic("%s: bad total length %ld", myname, (long) total);
if (msg_verbose > 1)
msg_info("%s: write total length %ld", myname, (long) total);
|| defined(FREEBSD5) || defined(FREEBSD6) || defined(FREEBSD7) \
|| defined(FREEBSD8) || defined(FREEBSD9) || defined(FREEBSD10) \
|| defined(FREEBSD11) || defined(FREEBSD12) || defined(FREEBSD13) \
- || defined(FREEBSD14) \
+ || defined(FREEBSD14) || defined(FREEBSD15) || defined(FREEBSD16) \
|| defined(BSDI2) || defined(BSDI3) || defined(BSDI4) \
|| defined(OPENBSD2) || defined(OPENBSD3) || defined(OPENBSD4) \
|| defined(OPENBSD5) || defined(OPENBSD6) || defined(OPENBSD7) \
+ || defined(OPENBSD8) \
|| defined(NETBSD1) || defined(NETBSD2) || defined(NETBSD3) \
|| defined(NETBSD4) || defined(NETBSD5) || defined(NETBSD6) \
|| defined(NETBSD7) | defined(NETBSD8) || defined(NETBSD9) \
- || defined(NETBSD10) \
+ || defined(NETBSD10) || defined(NETBSD11) || defined(NETBSD12) \
|| defined(EKKOBSD1) || defined(DRAGONFLY)
#define SUPPORTED
#include <sys/param.h>
/*
* Helper macros... Note that there is no need to check the result from
- * VSTRING_SPACE() because that always succeeds or never returns.
+ * VSTRING_SPACE() because that always succeeds or never returns. 202406
+ * Claude: avoid integer overflow in field width computations.
*/
#ifndef NO_SNPRINTF
-#define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \
+#define VBUF_SNPRINTF(bp, width_or_prec, type_space, fmt, arg) do { \
ssize_t _ret; \
- if (VBUF_SPACE((bp), (sz)) != 0) \
+ if ((width_or_prec) > INT_MAX - (type_space)) \
+ msg_panic("vbuf_print: field width (%d + %lu) > INT_MAX", \
+ (width_or_prec), (unsigned long) (type_space)); \
+ if (VBUF_SPACE((bp), (width_or_prec) + (type_space)) != 0) \
return (bp); \
_ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \
if (_ret < 0) \
} while (0)
#define VSTRING_ADDNUM(vp, n) do { \
- VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \
+ VBUF_SNPRINTF(&(vp)->vbuf, 0, INT_SPACE, "%d", n); \
} while (0)
#define VBUF_STRCAT(bp, s) do { \
msg_panic("%s: %%l%c is not supported", myname, *cp);
s = va_arg(ap, char *);
if (prec >= 0 || (width > 0 && width > strlen(s))) {
- VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
vstring_str(fmt), s);
} else {
VBUF_STRCAT(bp, s);
case 'x':
case 'X':
if (long_flag)
- VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
vstring_str(fmt), va_arg(ap, long));
else
- VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE,
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec), INT_SPACE,
vstring_str(fmt), va_arg(ap, int));
break;
case 'e': /* float-valued argument */
case 'f':
case 'g':
/* C99 *printf ignore the 'l' modifier. */
- VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE,
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec), DBL_SPACE,
vstring_str(fmt), va_arg(ap, double));
break;
case 'm':
case 'p':
if (long_flag)
msg_panic("%s: %%l%c is not supported", myname, *cp);
- VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE,
+ VBUF_SNPRINTF(bp, (width > prec ? width : prec), PTR_SPACE,
vstring_str(fmt), va_arg(ap, char *));
break;
default: /* anything else is bad */