From: Wietse Z Venema Date: Fri, 1 May 2026 05:00:00 +0000 (-0500) Subject: postfix-3.9.10 X-Git-Tag: v3.9.10^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b4e493e3e7eac86e46fd41699ba2393dedcf8675;p=thirdparty%2Fpostfix.git postfix-3.9.10 --- diff --git a/postfix/HISTORY b/postfix/HISTORY index 3cf6d4027..358357ff1 100644 --- a/postfix/HISTORY +++ b/postfix/HISTORY @@ -28282,3 +28282,47 @@ Apologies for any names omitted. 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. diff --git a/postfix/makedefs b/postfix/makedefs index 14810b6dc..893208c36 100644 --- a/postfix/makedefs +++ b/postfix/makedefs @@ -347,6 +347,24 @@ case "$SYSTEM.$RELEASE" in : ${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 @@ -382,9 +400,18 @@ case "$SYSTEM.$RELEASE" in : ${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 ;; @@ -434,6 +461,22 @@ case "$SYSTEM.$RELEASE" in : ${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 diff --git a/postfix/src/global/dict_pgsql.c b/postfix/src/global/dict_pgsql.c index 89cb59f07..902c057e8 100644 --- a/postfix/src/global/dict_pgsql.c +++ b/postfix/src/global/dict_pgsql.c @@ -572,8 +572,10 @@ static void plpgsql_connect_single(DICT_PGSQL *dict_pgsql, HOST *host) 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; } diff --git a/postfix/src/global/dsn_util.c b/postfix/src/global/dsn_util.c index 52b997a33..57511280a 100644 --- a/postfix/src/global/dsn_util.c +++ b/postfix/src/global/dsn_util.c @@ -154,7 +154,7 @@ DSN_SPLIT *dsn_split(DSN_SPLIT *dp, const char *def_dsn, const char *text) 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; diff --git a/postfix/src/global/mail_version.h b/postfix/src/global/mail_version.h index 0142198dc..a627f3b67 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 "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 diff --git a/postfix/src/global/pipe_command.c b/postfix/src/global/pipe_command.c index 66aec8a67..a7a1d62a5 100644 --- a/postfix/src/global/pipe_command.c +++ b/postfix/src/global/pipe_command.c @@ -460,6 +460,11 @@ int pipe_command(VSTREAM *src, DSN_BUF *why,...) 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); /* diff --git a/postfix/src/tls/tls_prng_file.c b/postfix/src/tls/tls_prng_file.c index 23865be39..14feebe26 100644 --- a/postfix/src/tls/tls_prng_file.c +++ b/postfix/src/tls/tls_prng_file.c @@ -132,7 +132,8 @@ ssize_t tls_prng_file_read(TLS_PRNG_SRC *fh, size_t len) 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); } diff --git a/postfix/src/util/argv.c b/postfix/src/util/argv.c index 332426e88..15456766d 100644 --- a/postfix/src/util/argv.c +++ b/postfix/src/util/argv.c @@ -192,6 +192,9 @@ ARGV *argv_alloc(ssize_t len) 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; @@ -250,6 +253,9 @@ static void argv_extend(ARGV *argvp) { 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 *)); @@ -376,9 +382,10 @@ void argv_delete(ARGV *argvp, ssize_t first, ssize_t how_many) 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); diff --git a/postfix/src/util/dict_cdb.c b/postfix/src/util/dict_cdb.c index a9133cc35..d44571545 100644 --- a/postfix/src/util/dict_cdb.c +++ b/postfix/src/util/dict_cdb.c @@ -394,7 +394,8 @@ static DICT *dict_cdbm_open(const char *path, int dict_flags) } #ifndef NO_FTRUNCATE - if (st0.st_size) + /* 202604 Claude: use the post-myflock() fstat result. */ + if (st1.st_size) ftruncate(fd, 0); #endif diff --git a/postfix/src/util/dict_cidr.c b/postfix/src/util/dict_cidr.c index 726d11cd6..f8246b3e8 100644 --- a/postfix/src/util/dict_cidr.c +++ b/postfix/src/util/dict_cidr.c @@ -237,11 +237,14 @@ static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno, 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); } diff --git a/postfix/src/util/dict_db.c b/postfix/src/util/dict_db.c index 053db3c1e..4084561f2 100644 --- a/postfix/src/util/dict_db.c +++ b/postfix/src/util/dict_db.c @@ -446,8 +446,10 @@ static int dict_db_sequence(DICT *dict, int function, */ 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: diff --git a/postfix/src/util/dict_pcre.c b/postfix/src/util/dict_pcre.c index 20eee02c1..94b90c6dd 100644 --- a/postfix/src/util/dict_pcre.c +++ b/postfix/src/util/dict_pcre.c @@ -728,6 +728,9 @@ static int dict_pcre_compile(const char *mapname, int lineno, } 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); } diff --git a/postfix/src/util/dict_sockmap.c b/postfix/src/util/dict_sockmap.c index becad1ae8..8c6c29829 100644 --- a/postfix/src/util/dict_sockmap.c +++ b/postfix/src/util/dict_sockmap.c @@ -254,7 +254,8 @@ static const char *dict_sockmap_lookup(DICT *dict, const char *key) 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); diff --git a/postfix/src/util/midna_domain.c b/postfix/src/util/midna_domain.c index bc016b637..21d681617 100644 --- a/postfix/src/util/midna_domain.c +++ b/postfix/src/util/midna_domain.c @@ -189,11 +189,13 @@ static void *midna_domain_to_ascii_create(const char *name, void *unused_context */ 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); /* @@ -203,7 +205,7 @@ static void *midna_domain_to_ascii_create(const char *name, void *unused_context * "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", @@ -243,11 +245,13 @@ static void *midna_domain_to_utf8_create(const char *name, void *unused_context) */ 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); /* @@ -256,7 +260,7 @@ static void *midna_domain_to_utf8_create(const char *name, void *unused_context) * 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); diff --git a/postfix/src/util/netstring.c b/postfix/src/util/netstring.c index 0edd80e1b..471d33d78 100644 --- a/postfix/src/util/netstring.c +++ b/postfix/src/util/netstring.c @@ -301,14 +301,17 @@ void netstring_put_multi(VSTREAM *stream,...) 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); diff --git a/postfix/src/util/sys_defs.h b/postfix/src/util/sys_defs.h index 62749ab57..ab1b00a59 100644 --- a/postfix/src/util/sys_defs.h +++ b/postfix/src/util/sys_defs.h @@ -31,14 +31,15 @@ || 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 diff --git a/postfix/src/util/vbuf_print.c b/postfix/src/util/vbuf_print.c index d7a323f24..70bc35a99 100644 --- a/postfix/src/util/vbuf_print.c +++ b/postfix/src/util/vbuf_print.c @@ -102,12 +102,16 @@ /* * 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) \ @@ -132,7 +136,7 @@ } 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 { \ @@ -259,7 +263,7 @@ VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) 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); @@ -275,17 +279,17 @@ VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) 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': @@ -296,7 +300,7 @@ VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) 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 */