]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.10.9 v3.10.9
authorWietse Z Venema <wietse@porcupine.org>
Fri, 1 May 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Sat, 2 May 2026 08:49:30 +0000 (18:49 +1000)
19 files changed:
postfix/HISTORY
postfix/makedefs
postfix/src/dns/dns_lookup.c
postfix/src/global/dict_pgsql.c
postfix/src/global/dsn_util.c
postfix/src/global/mail_version.h
postfix/src/global/pipe_command.c
postfix/src/global/rfc2047_code.c
postfix/src/tls/tls_prng_file.c
postfix/src/util/argv.c
postfix/src/util/dict_cdb.c
postfix/src/util/dict_cidr.c
postfix/src/util/dict_db.c
postfix/src/util/dict_pcre.c
postfix/src/util/dict_sockmap.c
postfix/src/util/midna_domain.c
postfix/src/util/netstring.c
postfix/src/util/sys_defs.h
postfix/src/util/vbuf_print.c

index b715c1b3a6ad71356c897a5014718c150a17b684..393849d857706e25333f033ba627f2e730212224 100644 (file)
@@ -29279,3 +29279,52 @@ 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 3.10): The RFC 2047
+       encoder for the sender "full name" could loop when a very
+       long full_name_encoding_charset value was configured in
+       main.cf. Found by Claude Opus 4.6. File: global/rfc2047_code.c.
+
+       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:
+       dns/dns_lookup.c, 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.
index 6f6b64662ac318d94abb346134b365465bcb2829..7560a1c0b4dc7785392ccd2835c984f88a34cdb8 100644 (file)
@@ -351,6 +351,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
@@ -386,9 +404,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
                ;;
@@ -438,6 +465,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
index 03c42e763c4dd21551d8c82fad7441b439637ba9..d2db3188c166703e0a0b8a12935a7c78b6ca669d 100644 (file)
@@ -834,14 +834,15 @@ static int dns_get_rr(DNS_RR **list, const char *orig_name, DNS_REPLY *reply,
        for (src = pos, dst = (unsigned char *) ltemp;
             src < pos + fixed->length; /* */ ) {
            frag_len = *src++;
-           if (msg_verbose)
-               msg_info("frag_len=%d text=\"%.*s\"",
-                        (int) frag_len, (int) frag_len, (char *) src);
+           /* 202604 Claude: move debug logging after the frag_len check. */
            if (frag_len > reply->end - src
            || frag_len >= ((unsigned char *) ltemp + sizeof(ltemp)) - dst) {
                msg_warn("extract_answer: bad TXT string length: %d", frag_len);
                return (DNS_RETRY);
            }
+           if (msg_verbose)
+               msg_info("frag_len=%d text=\"%.*s\"",
+                        (int) frag_len, (int) frag_len, (char *) src);
            while (frag_len-- > 0) {
                ch = *src++;
                *dst++ = (ISPRINT(ch) ? ch : ' ');
index 81a11a167b2c1c3a0e66e58aeec1c7fd75bc3e44..838ab8c20425f7224a7e26c714335568848514eb 100644 (file)
@@ -574,8 +574,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;
     }
index 52b997a3320593bc2ab577442d38e7290e11a18f..57511280a5a0afe3360f055466e6e6f1a39604d3 100644 (file)
@@ -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;
index cfbde99677c400a2b0158a5be31a9ce576e8cdbc..a610e22e5cda8173a28e7230ca0cf83f7bbd90c9 100644 (file)
@@ -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.10.8"
+#define MAIL_RELEASE_DATE      "20260501"
+#define MAIL_VERSION_NUMBER    "3.10.9"
 
 #ifdef SNAPSHOT
 #define MAIL_VERSION_DATE      "-" MAIL_RELEASE_DATE
index 66aec8a67321d493e90dde1f7d9a695f69fe6261..a7a1d62a5f805b86ba21357399d93563aa23deee 100644 (file)
@@ -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);
 
        /*
index b7caaeab7fb6486e835eeca7635100b1f773884d..3a5fca0bb639963838c7de1df04b00dc9af969f4 100644 (file)
@@ -274,6 +274,12 @@ char   *rfc2047_encode(VSTRING *result, int header_context,
        msg_warn("%s: encoder called with empty charset name", myname);
        return (0);
     }
+    /* 202604 Claude: avoid 'space_left' underflow. */
+    if (strlen(charset) > ENC_WORD_MAX_LEN / 2) {
+       msg_warn("%s: unreasonable charset name: '%.100s'",
+                myname, charset);
+       return (0);
+    }
     for (cp = (const unsigned char *) charset; (ch = *cp) != 0; cp++) {
        if (!RFC2047_ALLOWED_TOKEN_CHAR(ch)) {
            msg_warn("%s: invalid character: 0x%x in charset name: '%s'",
index 23865be39db8df64bffa419542038e45210165bf..14feebe2629c1d67d7ffc12614ace7817dbf353c 100644 (file)
@@ -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);
 }
index 555a35ef9e3ae5bc17e0d03d9cf0c8c793bf397a..b502098187fe89a9d4c7f8d4e6d9a781ec76a0a9 100644 (file)
@@ -203,6 +203,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;
@@ -261,6 +264,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 *));
@@ -404,9 +410,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);
 
index ea98363ff100a0393f1acef2c6bacd9ead616430..da274f5a9e803f50ed58c723784339ae33a1e281 100644 (file)
@@ -456,7 +456,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
 
index 726d11cd6fb4117532d0d8a798f13a60310e307a..f8246b3e831c42c26f225479bf07b71561e7ed2f 100644 (file)
@@ -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);
 }
index 053db3c1edcc1cedda51131a6c2bf7617da3e342..4084561f2227adb5c755a0d8dd90ac13ee0fe296 100644 (file)
@@ -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:
index 20eee02c112c85c08f42ab9d015fbc956e3010ff..94b90c6dd10375d43b80b5d91542c98ba0ab5c34 100644 (file)
@@ -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);
 }
index 8c0d4e01453998b56e5c07d71d758dbcd57a05b6..66d5ef60605c023454dbcea48173b96871bee28d 100644 (file)
@@ -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);
index 5e9d6f8cbade55cefdf6392ad366d4bc86d185b8..018b537dbde4a23fe4999f524bce8f4e52eed02c 100644 (file)
@@ -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);
index ea68ccd34b42123def43d2dda0d398597a19e322..fe32ddf568f7d2fc12464ff44bb063540b38e183 100644 (file)
@@ -305,14 +305,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);
 
index 70aab2366975ce90d80083ac02ff9188165d84a3..74319f29d21946f9b5e42d1f91e55865d54e897b 100644 (file)
     || 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>
index 2e3266ed7105d24a57d1bab0a2fe7952c49fa5d9..0b8fe6bd449f0475de9cf9791c433d81b624fac1 100644 (file)
 
  /*
   * 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 { \
@@ -260,7 +264,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);
@@ -276,17 +280,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 */