Prereq: "3.9.9" diff -ur --new-file /var/tmp/postfix-3.9.9/src/global/mail_version.h ./src/global/mail_version.h --- /var/tmp/postfix-3.9.9/src/global/mail_version.h 2026-02-18 14:27:51.000000000 -0500 +++ ./src/global/mail_version.h 2026-05-01 16:49:08.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 "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 -ur --new-file /var/tmp/postfix-3.9.9/HISTORY ./HISTORY --- /var/tmp/postfix-3.9.9/HISTORY 2026-02-18 14:26:46.000000000 -0500 +++ ./HISTORY 2026-05-01 19:48:25.000000000 -0400 @@ -28282,3 +28282,47 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/makedefs ./makedefs --- /var/tmp/postfix-3.9.9/makedefs 2025-12-05 15:16:05.000000000 -0500 +++ ./makedefs 2026-05-01 16:37:37.000000000 -0400 @@ -347,6 +347,24 @@ : ${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 @@ : ${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 @@ : ${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 -ur --new-file /var/tmp/postfix-3.9.9/src/global/dict_pgsql.c ./src/global/dict_pgsql.c --- /var/tmp/postfix-3.9.9/src/global/dict_pgsql.c 2025-02-07 16:51:20.000000000 -0500 +++ ./src/global/dict_pgsql.c 2026-05-01 16:33:00.000000000 -0400 @@ -572,8 +572,10 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/global/dsn_util.c ./src/global/dsn_util.c --- /var/tmp/postfix-3.9.9/src/global/dsn_util.c 2006-01-07 20:28:37.000000000 -0500 +++ ./src/global/dsn_util.c 2026-05-01 16:35:30.000000000 -0400 @@ -154,7 +154,7 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/global/pipe_command.c ./src/global/pipe_command.c --- /var/tmp/postfix-3.9.9/src/global/pipe_command.c 2014-12-25 11:47:18.000000000 -0500 +++ ./src/global/pipe_command.c 2026-05-01 16:33:00.000000000 -0400 @@ -460,6 +460,11 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/tls/tls_prng_file.c ./src/tls/tls_prng_file.c --- /var/tmp/postfix-3.9.9/src/tls/tls_prng_file.c 2014-12-06 20:35:33.000000000 -0500 +++ ./src/tls/tls_prng_file.c 2026-05-01 16:33:00.000000000 -0400 @@ -132,7 +132,8 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/argv.c ./src/util/argv.c --- /var/tmp/postfix-3.9.9/src/util/argv.c 2024-01-24 19:03:04.000000000 -0500 +++ ./src/util/argv.c 2026-05-01 16:33:00.000000000 -0400 @@ -192,6 +192,9 @@ 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 @@ { 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 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/dict_cdb.c ./src/util/dict_cdb.c --- /var/tmp/postfix-3.9.9/src/util/dict_cdb.c 2021-12-19 10:12:38.000000000 -0500 +++ ./src/util/dict_cdb.c 2026-05-01 16:33:00.000000000 -0400 @@ -394,7 +394,8 @@ } #ifndef NO_FTRUNCATE - if (st0.st_size) + /* 202604 Claude: use the post-myflock() fstat result. */ + if (st1.st_size) ftruncate(fd, 0); #endif diff -ur --new-file /var/tmp/postfix-3.9.9/src/util/dict_cidr.c ./src/util/dict_cidr.c --- /var/tmp/postfix-3.9.9/src/util/dict_cidr.c 2023-04-16 16:41:47.000000000 -0400 +++ ./src/util/dict_cidr.c 2026-05-01 16:33:00.000000000 -0400 @@ -237,11 +237,14 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/dict_db.c ./src/util/dict_db.c --- /var/tmp/postfix-3.9.9/src/util/dict_db.c 2025-10-23 19:46:29.000000000 -0400 +++ ./src/util/dict_db.c 2026-05-01 16:33:45.000000000 -0400 @@ -446,8 +446,10 @@ */ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/dict_pcre.c ./src/util/dict_pcre.c --- /var/tmp/postfix-3.9.9/src/util/dict_pcre.c 2023-04-16 16:42:29.000000000 -0400 +++ ./src/util/dict_pcre.c 2026-05-01 16:33:00.000000000 -0400 @@ -728,6 +728,9 @@ } 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/dict_sockmap.c ./src/util/dict_sockmap.c --- /var/tmp/postfix-3.9.9/src/util/dict_sockmap.c 2020-09-13 11:18:21.000000000 -0400 +++ ./src/util/dict_sockmap.c 2026-05-01 16:33:00.000000000 -0400 @@ -254,7 +254,8 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/midna_domain.c ./src/util/midna_domain.c --- /var/tmp/postfix-3.9.9/src/util/midna_domain.c 2023-10-12 11:34:40.000000000 -0400 +++ ./src/util/midna_domain.c 2026-05-01 16:33:00.000000000 -0400 @@ -189,11 +189,13 @@ */ 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 @@ * "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 @@ */ 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 @@ * 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/netstring.c ./src/util/netstring.c --- /var/tmp/postfix-3.9.9/src/util/netstring.c 2019-10-13 11:32:18.000000000 -0400 +++ ./src/util/netstring.c 2026-05-01 16:33:00.000000000 -0400 @@ -301,14 +301,17 @@ 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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/sys_defs.h ./src/util/sys_defs.h --- /var/tmp/postfix-3.9.9/src/util/sys_defs.h 2024-02-06 12:02:38.000000000 -0500 +++ ./src/util/sys_defs.h 2026-05-01 16:37:37.000000000 -0400 @@ -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 -ur --new-file /var/tmp/postfix-3.9.9/src/util/vbuf_print.c ./src/util/vbuf_print.c --- /var/tmp/postfix-3.9.9/src/util/vbuf_print.c 2020-11-22 12:57:24.000000000 -0500 +++ ./src/util/vbuf_print.c 2026-05-01 16:33:00.000000000 -0400 @@ -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 @@ 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 @@ 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 @@ 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 */